ITPub博客

首页 > Linux操作系统 > Linux操作系统 > Cairngorm tutorials from CODING ANGRY

Cairngorm tutorials from CODING ANGRY

原创 Linux操作系统 作者:a99tiger 时间:2009-05-23 22:26:22 0 删除 编辑
Cairngorm - First Steps

What is Cairngorm and is it for me?

Actionscript. seems to be a brave new world for developers; the rules are loose, the compiler is forgiving and there are many ways to get the job done. From application to application I have seen perfect object-oriented architectures, overly complex ego coding, and sloppy spaghetti code. When you are sitting down to design an application, consider existing code and design patterns. Why reinvent the wheel? The Flex architecture makes many patterns available to you and third-party frameworks (micro-architectures) are available for free.

This article and subsequent tutorials will explore Cairngorm, a micro-architecture developed by Adobe Consulting. This is a far cry from being the only or the definitive guide to Cairngorm. This article does, however, explain Cairngorm as I see it and have used it on many projects. According to the documentation, Cairngorm is an architecture based on the MMVC Model. It is specifically designed to facilitate complex state and data synchronization between the client and the server, while keeping the programming of the View layer detached from the data implementation. To me it is a set of features that fill in the blanks where the Flex platform. left off.

Download the Cairngorm framework here.

Below is not a complete feature set, but they are the features I find most useful:

Application Organization

When starting a new Flex/Air project, the first thing I do is drop the Carngorm.swf into my lib folder and set up the following directory structure:

src
    assets
    commands
    control
    events
    model
    view
    vo    

“Assets” holds images, sounds, fonts, etc…

“Commands” holds the Cairngorm command classes.

“Control” holds the Cairngorm core classes. I usually add my delegates in here though they may be better suited in a “business” folder.

“Events” - I am going to assume you have an idea of what goes in here.

“Model” holds at least one singleton that keeps the state of the application.

“View” holds all the screens and their components.

“Vo” is used to hold value objects - simple POJOs (though this is Actionscript)

Application State

Tracking the application state in a single model is invaluable. I may be exaggerating here but it comes in handy all over the place.

Publish/Subscribe Event Model

The default event model in Flex/AIR is “bubble and capture.” The model works great until you need to send a message somewhere other than your parent. Cairngorm provides the ability to send an message from anywhere to anywhere in your application.

Discreet Commands

The commands in Cairngorm are, for the lack of a better word, awesome. They allow you, the developer, to execute specific actions based on specific events. This pattern allows you to map out your application development well before you write any code. It also raises the maintainability of your code and makes debugging easy.

Encapsulated data layer

This section of the Cairngorm framework acts like a doorway to your data source. It is possible to access http data, rtmp data and local XML all in the same application.

-

Now to the code. I hope this was a helpful and gentle introduction to the Cairngorm framework. The features outlined will soon become some of your most reliable and favorite coding tools.

Cairngorm - 1 Data Binding

In my introduction to Cairngorm I covered a list of features that I have found very handy. One of the most useful, and easiest to understand, is Cairngorm’s ability to maintain application state. Before adopting Cairngorm, I found myself passing data all over my applications. Not only was it hard to track down bugs, it made the application tightly bound. Cairngorm uses a singleton to keep application state. This is achieved though an empty interface called ModelLocator. Empty Interface? Yes, that’s right, it’s an empty interface that isn’t referenced anywhere. At first I was puzzled by this, and left it out of my code. After several projects, I decided to add the ModelLocator reference back into my classes. Although it is useless and not needed, it does let you know right away that the class is a singleton.

View The Example
View The Source
View The Whole Lesson

So what should your model classes look like? Here’s a singleton pattern with an interface reference:

// SampleModel.as

    
package ca.
model
    {
        import com
.adobe.cairngorm.model.ModelLocator
;
        
import mx.collections.ArrayCollection
;
        
        
[Bindable]
        public 
class 
SampleModel implements ModelLocator
        {
            protected 
static var instance : SampleModel 
;        
            
            
public var listData 
: Array;
            
            
public static function getInstance() : 
SampleModel
            { 
                
if ( instance == null 
)
                    
instance = new SampleModel 
(); 
                
                return 
instance
; 
            
}                    
            
            public 
function SampleModel
()
            
{
                
if ( instance != null 
) 
                    
throw new Error( "Only one SampleModel instance should be created" 
);
                
                
listData 
= new Array();
            
}
        }    
    } 

A bit of a tangent, but worth covering: You might notice is that the constructor is public. ActionScript. doesn’t allow for any constructor modifier other than “public.” Also the class throws an error if the constructor is called more than once. In fact, you should never call the constructor. Always use the getInstance() method. This ensures that the constructor is only called on time.

The following example demonstrates passing data via bindings as well as using the ModelLocator. As I mentioned before, the bindings method forces your application to be tightly bound and that is a bad thing.

// Tutorial_1_Bindings.mxml

    // Some Random Data
    
[Bindable]
    public 
var listData : Array = 
[
        {id
:0,title:"Apple"}
,
        
{id:1,title:"Banana"}
,
        
{id:2,title:"Kiwi"}
,
        
{id:3,title:"Lemon"}
,
        
{id:4,title:"Mango"}
,
        
{id:5,title:"Strawberry"} ]; 

When using the bindings method to pass data, you assign the listData Array to the listData parameter in your AngryBindingPanelL1:

// Tutorial_1_Bindings.mxml
    
    
<AngryBindingPanelL1 listData="{listData}" /> 

The parameter listData is declared in AngryBindingPanelL1 as a public bindable property:

// AngryBindingPanelL1.mxml
    
    
[Bindable]           
    public 
var listData : Array; 

Then this data is passed to the AngryBindingPanelL2 class. Yet another bindable property. As you can see, these bindable variables are just conduits for data. The classes declaring them don’t use them, and the classes’ knowledge of their children is a bit suspect, causing the application to become tightly bound:

// AngryBindingPanelL1.mxml
    
   
<containers:AngryBindingPanelL2 listData="{listData}" /> 

My apologies for showing you this again, but this is the code found in AngryBindingPanelL2. If you think it looks like what is in AngryBindingPanelL1, that’s because it does:

// AngryBindingPanelL2.mxml
    
    
[Bindable]           
    public 
var listData : Array; 

Lastly in order to get our data to the menu, we just bind it to the ComboBox:

// AngryBindingPanelL2.mxml

    
<mx:ComboBox dataProvider="{ listData }" labelField="title" /> 

In case you weren’t able to follow all that, there is a binding chain that looks like the following Application.listData -> AngryBindingPanelL1.listData -> AngryBindingPanelL2.listData -> ComboBox.dataProvider. We had to add a listData parameter in AngryBindingPanelL1 and in AngryBindingPanelL2 even though those classes never used it. 

Now let’s see what the ModelLocator method brings to the table. There is an additional step in the Application, we set the data in the model. Later you will see us setting data from anywhere we want, but for this example we are going to set the data in the init function. This is the same Array from before, but this time it has the init function below it:

// Tutorial_1_Bindings.mxml
    
    
public function init() : 
void
    {
        SampleModel
.getInstance().listData = new ArrayCollection( listData 
);
    
} 

Instead of passing the listData to the AngryBindingPanelL1, and then the AngryBindingPanelL2, we are simply setting the data in the init function and forgetting about it. This is nice because it frees you to set and get data where it is relevant in the application, and keep classes not involved with the data out of the mix. Our data is set so now all we have to do is access it in the right spot. In our case that spot is the dataProvider on the ComboBox:

//  Tutorial_1_Bindings.mxml
    
    
<mx:ComboBox dataProvider="{ SampleModel.getInstance().listData }"   labelField="title" /> 

That’s it! Next time you find yourself passing data through objects that could care less about the data, think about using the ModelLocator. Happy Coding!

View The Example
View The Source
View The Whole Lesson

Cairngorm - 2 Events

In the previous tutorial we covered the use of the ModelLocator and showed how it can be used to decouple a tightly bound application. Here we are going to cover the Cairngorm event model. Flex uses a Bubble/Capture event model which has some limitations. The biggest drawback is that you can only listen to events fired by your children and their descendants. Cairngorm provides a Publish/Subscribe model that lets you further decouple your application. Let’s say you you have a menu in a navigation bar on the left side of your application and another menu in another navigation bar on the right side of your application. Let’s further suppose that those two menus each need to know the state of the other. In native Flex, the highest common parent would need to listen for events thrown from each menu, capture those events and, as in the example presented in the last tutorial, pass the event data back down the chain on the other side. We just decoupled this type of binding for data, and now we find it emerging again for events. Events are fleeting and it seems a bit much to declare every event as a property on our model. So let’s use the CairngormEventDispatcher to get us around this problem.

View The Example
View The Source
View The Whole Lesson

Again we are going to cover the Flex-native way of accomplishing this, and compare it to the Cairngorm way of doing things. Since embracing Cairngorm, my applications have used a mix of Bubble/Capture and Publish/Subscribe event handling. You will find in your own coding that there are strong arguments for and against using each. As you build applications, you will find out which is right for you, when.

Let’s start with how Flex would keep two sibling (or cousin) menus aware of each other. Building on the last tutorial, we will use the ModelLocator method to populate some ComboBoxes. Once they’re populated, we’ll use these ComboBoxes to send events. Let’s start at the top, our highest common parent - in this case, it’s the Application. In order for the Application to handle the events, it needs to attach some listeners for MENU_CHANGE. The listener assignments are in the init function. There is one assignment for each of the two sides. In this case, those sides are the leftPanel and rightPanel declared here in MXML:

// Tutorial_2_Events.mxml
    
    
<mx:Script
>
        
[CDATA[
    
        public 
static const MENU_EVENT : String = "menuEvent"
;
    
        
public function init() : 
void
        {
            SampleModel
.getInstance().listData = new ArrayCollection( listData 
);
            
            
// Register the listeners and handlers for the Bubble/Capture events.
            
leftPanel.addEventListener( MENU_EVENT, leftBubbleHandler 
);
            
rightPanel.addEventListener( MENU_EVENT, rightBubbleHandler 
);
        
}
        
        ]]
>
    mx:Script
>
    
    
    <
mx:HBox horizontalGap="20"
>
    
        <
AngryPanelT2L1 id="leftPanel" 
/>    
        
        <
AngryPanelT2L1 id="rightPanel" 
/>    
    
    mx:HBox> 

Next we will move to the bottom - this is where the events are generated. We start by capturing the change event on the ComboBox - we will call a function and eventually fire a new event of type MENU_CHANGE. In the last line below, you can see the assignment of the change event to our bubbleChange function:

// AngryPanelT2L2.mxml
        
    
<mx:Script
>
    
[CDATA[
       
        public 
function bubbleChange() : 
void
        {
            
var event : DataEvent = new DataEvent( Tutorial_2_Events.MENU_EVENT, true 
);
            
event.data = bubbleBox.selectedLabel
;
            
            
bubbleBox.dispatchEvent( event 
);
        
}
        
    ]]
>
    mx:Script
>    
        
    <
mx:
ComboBox 
        id
=
"bubbleBox"
        
dataProvider=
"{SampleModel.getInstance().listData}" 
        
labelField=
"title" 
        
rowCount=
"6" 
        
change="bubbleChange()" /> 

Inside the bubbleChange function, we are dispatching a generic Flash DataEvent of type MENU_CHANGE. You will notice we are using the bubbleBox (our ComboBox) to dispatch the new event. This provides scope to the event, so we can later tell which side sent the event. The bubbleBox is passed as the event.target to our event handler. One more thing to notice is that when we create our event, its bubbles flag it set to “true.” Bubbling is mostly considered rude, but to simplify this demo I decided to make an exception. If bubbles was set to “false,” we would be forced to catch the MENU_CHANGE event in every parent, and redispatch it all the way to the top.

Back to the application now for the event handling. Below are the two event handlers that listen for MENU_CHANGE events. Each of these event handlers catches an event, pulls out the valuable data - in our case, the selectedLabel - and, using the binding method covered in the last tutorial, forces the label down the other side:

// AngryPanelT2L2.mxml
    
    
public function leftBubbleHandler( event : DataEvent ) : 
void
    {
        
// Catch the event and use bindings to push that value down the chain to the other side
        
rightPanel.eventData = event.data
;
    
}
    
    public 
function rightBubbleHandler( event : DataEvent ) : 
void
    {
        
// Catch the event and use bindings to push that value down the chain to the other side
        
leftPanel.eventData    = event.data
;
    
} 

This is not good code. The Application has attached listeners for events it doesn’t need to be bothered with. It needs to know what to pull out of the event, and it needs to change values on its children, who in turn need to update their children. Since we did away with tight bindings in the last tutorial, perhaps you are thinking we should set values on a model. This would be cleaner, and would do away with the tightly bound relationships, but we can go one step further.

Cairngorm provides a Publish/Subscribe event model. This means only the objects interested in specific events need to listen and handle them. Making our Application free of all event listening and handling. This event model is more like a direct connection to your target(s). This model has many advantages, one of which is being able to have multiple handlers for a single event.

This example starts with the ComboBox dispatching the “change” event. Again, look at the last line, where the assignment of the “change” event is bound to an event handler. The only difference from last time is that we are calling a different function:

// AngryPanelT2L2.mxml
        
    
<mx:
ComboBox 
        id
=
"publishBox"
        
dataProvider=
"{SampleModel.getInstance().listData}" 
        
labelField=
"title" 
        
rowCount=
"6"
        
change="publishChange()"/> 


The difference, in the Cairngorm example, is that all the event handling is done in the class that contains the ComboBoxes. Here, we are adding an event listener for MENU_CHANGE to the CairngormEventDispatcher - this is similar to what we did, using event registration, in the Application for the native Flex DataEvent. Here, events are dispatched and assigned to handlers through the CairngormEventDispatcher. This is what provides the Publish/Subscribe functionality:

// AngryPanelT2L2.mxml

    
public function init() : 
void
    {
        CairngormEventDispatcher
.getInstance().addEventListener( Tutorial_2_Events.MENU_EVENT, publishHandler 
);
    
} 

The two functions below are all it takes to dispatch and handle the Cairngorm events. There is an event handler for the native Flex “change” event which lets us know its time to dispatch an CairngormEvent. You will notice that the event being constructed is a CairngormEvent, not a DataEvent. CairngormEvent has a “data” field. This field comes in very handy. Unless your event has some substantial data, you can typically get away with using the vanilla CairngormEvent. In the native example, the ComboBox dispatched the event so that it would wind up as the “event.target.” In this case, we set the data field to the ComboBox:

// AngryPanelT2L2.mxml
    
    
public function publishChange() : 
void
    {
        
var event : CairngormEvent = new CairngormEvent( Tutorial_2_Events.MENU_EVENT 
);
        
event.data = publishBox
;
        
CairngormEventDispatcher.getInstance().dispatchEvent( event 
);
    
}
    
    public 
function publishHandler( event : CairngormEvent ) : 
void
    {
        
// Since we have multiple instances of the same class recieving Cairngorm events
        // we need to figure out who threw the event and take appropriate action
        
if( event.data != publishBox 
)
        
{
            publishLabel
.text = "Recieved: " +  ComboBox( event.data ).selectedLabel
;
        
}
    } 

The publishHandler in this case is being passed a CairngormEvent, not a DataEvent. Since we are using the same class on both sides of our example application, we need a way to figure out who sent the event. As I mentioned before you can have multiple handlers for each event. And in this example, both sides of the application receive the MENU_CHANGE event. In order to tell which side sent the event, we compare the “event.data” to the publishBox in our class. If the publishBox is not a child of the class, that means the event came from the other side.


Conclusion

This example shows how you can use the Flex native and the Cairngorm event models side by side. Each is suited for various types of functionality, and each has its advantages and disadvantages. Once you are comfortable with both models, your applications will become cleaner and easier to maintain. Happy coding!

View The Example
View The Source
View The Whole Lesson

Cairngorm - 3 Commands

In the previous tutorial we covered CairngormEvents. With their Publish/Subscribe model event dispatchers, we were able to communicate directly with event handlers (more or less). In this tutorial, we are going to build upon how Cairngorm handles events. As in the last tutorial, we will be using the CairngormEventDispatcher to dispatch events. We are still free to use the Cairngorm EventDispatcher to listen to events, but this time we won’t. Cairngorm provides the FrontController to map specific events to specific Command classes. As we did before, we are able to assign multiple handlers to a single event.

View The Example
View The Source
View The Whole Lesson

To get started using the Cairngorm FrontController, it is necessary to include it in your Application. I almost always include it as one of the first MXML items in my file.

// Tutorial_3.mxml
    
    
<!-- Cairngorm Initialization 
-->
    <
control:AngryController id="angryController" /> 

Now that we have a controller in our Application, we need to make some event mappings to commands. The FrontController has only one public method, called “addCommand”. This function maps an event to a command, working the way a call to addEventListener works on other classes. But instead of mapping an event to a function, it maps a command to a function. Commands, in this case, can be thought of as event handlers.

// AngryController.as
    
    
public class AngryController extends 
FrontController 
    {
        public 
function AngryController():
void    
        {
            super
();
            
            
// Used in Tutorial 3
            
addCommand( TutorialEvent.ADD_ITEM,        AddItemCommand 
);
            
addCommand( TutorialEvent.REMOVE_ITEM,    RemoveItemCommand 
);    
         
}
    } 

Notice that the event types are being brought in from the events themselves. This is a stylistic decision, and you are free to get the event types from wherever you want. Whatever you decide, make sure they are constants. I like having the event types declared in the events that carry the data for those events.

Now that we have our mappings, it’s time to write our event handlers (commands). Commands, in their simplest form, must implement ICommand, and contain an “execute” function.  The first event handler (command) we will be writing is AddItemCommand. As the name implies, it adds an item to a list.

// AddItemCommand.as

    
public class 
AddItemCommand implements Command
    {
        public 
function execute( event : CairngormEvent ):
void 
        {    
            
var model : SampleModel = SampleModel.getInstance
();
            
            if( 
event.data != null 
)
            
{
                model
.listData.addItem( event.data 
);    
            
}
            
        }        
   
    } 

As we discussed in the previous tutorial, CairngormEvent has a data field. AddItemCommand will look in that data field for the object to add to the list. If the data field’s value is not null, it is inserted to the list. That’s all. AddItemCommand isn’t a very complex class, but it is a nice compartmentalized unit of work. The next task at hand is writing RemoveItemCommand:

// RemoveItemCommand.as

    
public class 
RemoveItemCommand implements Command
    {
        public 
function execute( event : CairngormEvent ):
void 
        {    
            
var target : String = String( event.data 
);
            
            var 
model : SampleModel = SampleModel.getInstance
();
            
            for 
each( var item : Object in model.listData 
)
            
{
                
if( item.id == target 
)
                
{
                    model
.listData.removeItemAt( model.listData.getItemIndex( item 
));
                    return;
                
}    
            }
        }
    } 

RemoveItemCommand looks a whole lot like AddItemCommand, in that it isn’t a very complex class. This time, the data field in the CairngormEvent is being used to send the id of the item to be deleted. If the id is found in the list, that item is removed.

One thing I really like about this approach to writing applications: you can sit down at the beginning, and, depending on your feature set, get most if not all your command writing out of the way. Another upside is that you know where to look first if, God forbid, you have bugs in your system.

All that is left for this lesson is to exercise our new commands. This time we are going to be, you guessed it, adding and removing items from a list. To do this, we add the following components below a List. Not that as in our past tutorials, our list is being populated via a model.
 

// AngryPanelT3L2.mxml
    
    
<mx:HBox
>
        <
mx:TextInput id="itemName" width="100" 
/>
        <
mx:Button label="Add Item" click="addItem()" enabled="{itemName.text.length > 0}" 
/>
    mx:HBox
>
    
    <
mx:HBox
>
        <
mx:
ComboBox 
            id
=
"removeBox"
            
width=
"100"
            
dataProvider=
"{model.listData}" 
            
labelField=
"title" 
            
rowCount="6" 
/>
            
        <
mx:Button label="Remove Item" click="removeItem()" enabled="{model.listData.length > 0}" 
/>    
    mx:HBox> 

Inside the first Hbox, you will see a TextInput and a Button. Anything you type in the TextInput will become an item in the list.

Below the HBox, you will see a ComboBox and a Button. This ComboBox has its data bound to the model as well. When you choose an item from the list and click the Button, the item will be removed from the list. Both Buttons call functions in the Actionscript, to build the list and dispatch the events:

// AngryPanelT3L2.mxml
     
    
public function addItem() : 
void
    {
        
var d : Date = new Date
();
            
        var 
event : TutorialEvent = new TutorialEvent( TutorialEvent.ADD_ITEM 
);
        
event.data = {id:d.getUTCMinutes(), title:itemName.text}
;
        
CairngormEventDispatcher.getInstance().dispatchEvent( event 
);
    
}
    
    
    public 
function removeItem() : 
void
    {
        
var event : TutorialEvent = new TutorialEvent( TutorialEvent.REMOVE_ITEM 
);
        
event.data = removeBox.selectedItem.id
;
        
CairngormEventDispatcher.getInstance().dispatchEvent( event 
);
    
} 

These two functions make TutorialEvents of types that match what we put in our mappings at the beginning of this lesson. Based on our mappings in the FrontController, certain event handlers (the ones we wrote earlier) will be called. That’s it!

(Note that the function addItem includes some code that might look like mumbo jumbo, involving “Date.” This bit is used to create a unique id for each event.)


Conclusion

To run down what just happened: we made mappings and tied event types to event handlers (commands). Our commands updated values on the model. Our UI is tied to values in the model, and was automatically updated when the commands did their job.

I highly suggest incorporating commands into your application as they are a great way to encapsulate the functionality of your application. Happy Coding!

View The Example
View The Source
View The Whole Lesson

from

http://codingangry.com/index.php/site/comments/cairngorm_1/

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/130323/viewspace-600882/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2009-05-23

  • 博文量
    2
  • 访问量
    150012