Chapter 14. Tide client framework

Chapter 14. Tide client framework

14.1. Getting Started
14.2. Application Initialization
14.3. Contexts and Components
14.4. Dependency Injection
14.5. Event Bus
14.6. Conversations
14.7. Integration with Data Management
14.8. Extension and Plugins
14.9. Security
14.10. Modules
14.11. Support for Deep Linking

GraniteDS comes with a built-in client framework named Tide that is based on the concept of contextual components and serves as the basis for many advanced features (such as data management or simplified remoting).

This client framework implements some classic features of usual Java frameworks such as Spring or Seam and provides a programming model that should look familiar to Java developers. However it tries to be as transparent and unobtrusive as possible and yet stay close from the Flex framework concepts and traditional usage.

The framework features notably:

To get started quickly and see what Tide can do, let's see how we can Tidify a simple Hello World example in a few steps. In a first iteration, we do everything in one simple MXML:



<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    preinitialize="Tide.getInstance().initApplication()">

    <mx:Script>
        import org.granite.tide.Tide;
        import org.granite.tide.Component;
        import org.granite.tide.events.TideResultEvent;
        
        [In]
        public var helloService:Component;
        
        private function hello():void {
            helloService.hello(iname.text, helloResult);
        }
        
        private function helloResult(event:TideResultEvent):void {
            lmessage.text = event.result as String;
        }
    </mx:Script>
    
    <mx:TextInput id="iname"/>
    <mx:Button label="Hello" click="hello()"/>
    <mx:Label id="lmessage"/>
</mx:Application>

       

This is almost exactly the same example that we have seen in the Tide Remoting chapter. In this first step we see only the injection feature of Tide with the annotation [In] that can be placed on any writeable property of the component (public or with a setter).

Tide will by default consider any injection point with a type Component (or any type extending Component) as a remoting injection and will create and inject a client proxy for a service named helloService (by default the name of the property is used as the name of the injected component).

This is more than enough for a Hello World application, but if we continue the application this way, everything will be in the same MXML. So in a first iteration we want at least to separate the application initialization part and the real UI part. We can for example create a new MXML file named Hello.mxml:



<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    preinitialize="Tide.getInstance().initApplication()">

    <mx:Script>
        import org.granite.tide.Tide;
    </mx:Script>
    
    <Hello id="hello"/>
</mx:Application>
       

Hello.mxml:



<?xml version="1.0" encoding="utf-8"?>
<mx:Panel
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*">
    
    <mx:Metadata>[Name]</mx:Metadata>

    <mx:Script>
        import org.granite.tide.Component;
        import org.granite.tide.events.TideResultEvent;
        
        [In]
        public var helloService:Component;
        
        private function hello():void {
            helloService.hello(iname.text, helloResult);
        }
        
        private function helloResult(event:TideResultEvent):void {
            lmessage.text = event.result as String;
        }
    </mx:Script>
    
    <mx:TextInput id="iname"/>
    <mx:Button label="Hello" click="hello()"/>
    <mx:Label id="lmessage"/>
</mx:Panel>
       

This is a bit better, the main UI is now defined in its own MXML. Tide has not much to do here, but note that we have added the [Name] metadata annotation on the MXML to instruct Tide that it has to manage the component and inject the dependencies on properties marked with [In]. This was not necessary in the initial case because the main application itself is always registered as a component managed by Tide (under the name application).

The next step is to decouple our UI component from the server interaction, so we can for example reuse the same UI component in another context or simplify the testing of the server interaction with a mock controller.

The first thing we can do is introduce a controller component (the C in MVC) that will handle this interaction and an interface so that we can easily switch the controller implementation. Then we can just bind it to the MXML component:

     
package com.myapp.controller {

    [Bindable]
    public interface IHelloController {
    
        function hello(name:String):void;
        
        function get message():String;
    }
}
        
package com.myapp.controller {

    import org.granite.tide.Component;
    import org.granite.tide.events.TideResultEvent;

	[Name("helloController")]
	public class HelloController implements IHelloController {
	    
	    [In]
	    public var helloService:Component;
	    
	    [Bindable]
	    public var message:String;
	    
	    public function hello(name:String):void {
	        helloService.hello(name, helloResult);
	    }
	    
	    private function helloResult(event:TideResultEvent):void {
	        message = event.result as String;
	    }
	}
}
	   

We have to configure the controller in the main MXML and use it in the view:



<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    preinitialize="Tide.getInstance().initApplication()">

    <mx:Script>
        import org.granite.tide.Tide;
        
        Tide.getInstance().addComponents([HelloController]);
    </mx:Script>
    
    <Hello id="hello"/>
</mx:Application>
       


<?xml version="1.0" encoding="utf-8"?>
<mx:Panel
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*">
    
    <mx:Metadata>[Name]</mx:Metadata>

    <mx:Script>
        import com.myapp.controller.IHelloController;
        
        [Bindable] [Inject]
        public var helloController:IHelloController;
    </mx:Script>
    
    <mx:TextInput id="iname"/>
    <mx:Button label="Hello" click="helloController.hello(iname.text)"/>
    <mx:Label id="lmessage" text="{helloController.message}"/>
</mx:Panel>
       

This is already quite clean, and completely typesafe. The annotation [Inject] indicates that Tide should inject any managed component which class extends or implements the specified type, contrary to the annotation [In] that is used to inject a component by name. Here the instance of HelloController will be injected, in a test case you could easily configure an alternative TestHelloController implementing the same interface.

This kind of architecture is inspired by JSF (Java Server Faces) and works fine. However there is still a bit of coupling between the views and the controllers, and it does not really follow the usual event-based style of the Flex framework. To obtain a more pure MVC model, we have to add a model component that will hold the state of the application, and an event class dispatched through the Tide event bus to decouple the view and the controller:

package com.myapp.events {

    import org.granite.tide.events.AbstractTideEvent;
    
    public class HelloEvent extends AbstractTideEvent {
    
        public var name:String;
        
        public function HelloEvent(name:String):void {
            super();
            this.name = name;
        }
    }
}
       
     
package com.myapp.model {

    [Bindable]
    public interface IHelloModel {
    
        function get message():String;
        
        function set message(message:String):void;
    }
}
        
package com.myapp.model {

    [Name("helloModel")]
    public class HelloModel implements IHelloModel {
        
        [Bindable]
        public var message:String;
    }
}
       

The controller will now observe our custom event, and set the value of the message property in the model:

package com.myapp.controller {

    import org.granite.tide.Component;
    import org.granite.tide.events.TideResultEvent;
    import com.myapp.events.HelloEvent;

    [Name("helloController")]
    public class HelloController implements IHelloController {
        
        [In]
        public var helloService:Component;
        
        [Inject]
        public var helloModel:IHelloModel;
        
        [Observer]
        public function hello(event:HelloEvent):void {
            helloService.hello(event.name, helloResult);
        }
        
        private function helloResult(event:TideResultEvent):void {
            helloModel.message = event.result as String;
        }
    }
}
       

Lastly we configure the new model component and dispatch the custom event from the UI:



<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    preinitialize="Tide.getInstance().initApplication()">

    <mx:Script>
        import org.granite.tide.Tide;
        
        Tide.getInstance().addComponents([HelloController, HelloModel]);
    </mx:Script>
    
    <Hello id="hello"/>
</mx:Application>
       


<?xml version="1.0" encoding="utf-8"?>
<mx:Panel
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*">
    
    <mx:Metadata>[Name]</mx:Metadata>

    <mx:Script>
        import com.myapp.events.HelloEvent;
        import com.myapp.model.IHelloModel;
        
        [Bindable] [Inject]
        public var helloModel:IHelloModel;
    </mx:Script>
    
    <mx:TextInput id="iname"/>
    <mx:Button label="Hello" click="dispatchEvent(new HelloEvent(iname.text))"/>
    <mx:Label id="lmessage" text="{helloModel.message}"/>
</mx:Panel>
       

The main difference here is that we use an event to communicate between the view and the controller. This would allow for example many controllers to react to the same user action. The view does not know which component will handle the event, and the controllers simply specify that they are interested in the event HelloEvent with the annotation [Observer] on a public handler method. Tide automatically wires the dispatcher and the observers through its event bus by matching the event type.

Note that the HelloEvent class extends a (pseudo) abstract class of the Tide framework. If you don't want any such dependency, you can use any Flex event but then you have to add an annotation [ManagedEvent] on the dispatcher to instruct Tide which events it has to manage. See more below in the section Event Bus.

Now we have a completely decoupled and testable architecture, however everything is wired typesafely, meaning that any error will be detected at compile time and not at runtime.

With some Java server frameworks (Spring and CDI) we can even achieve complete client/server type safety by generating a typed client proxy. The controller would then look like:

package com.myapp.controller {

    import org.granite.tide.Component;
    import org.granite.tide.events.TideResultEvent;
    import com.myapp.events.HelloEvent;
    import com.myapp.service.HelloService;

    [Name("helloController")]
    public class HelloController implements IHelloController {
        
        [Inject]
        public var helloService:HelloService;
        
        [Inject]
        public var helloModel:IHelloModel;
        
        [Observer]
        public function hello(event:HelloEvent):void {
            helloService.hello(event.name, helloResult);
        }
        
        private function helloResult(event:TideResultEvent):void {
            helloModel.message = event.result as String;
        }
    }
}
       

Hopefully you have now a relatively clear idea on what it's all about. The following sections will describe all this in more details.

The framework mainly consists in a global singleton object that holds the full configuration of the application. This singleton of type Tide has to be initialized in the preinitialize handler of the main application:



<mx:Application ...
    preinitialize="Tide.getInstance().initApplication()">
    ...
</mx:Application>

       

For example with Spring:



<mx:Application ...
    preinitialize="Spring.getInstance().initApplication()">
    ...
</mx:Application>

       

The Tide framework makes heavy use of Flex annotations, so you will need to configure your build system (Flash Builder, Ant or Maven) correctly so the Flex compiler keeps the necessary annotations at runtime (see the Project Setup chapter for Ant, Maven and Flash Builder).

The core concepts of the Tide framework are the context and the component.

Components are stateful objects that can be of any ActionScript 3 class with a default constructor and can have a unique instance stored in each context of the application. Usually components have a name so they can be referenced easily.

There are two main kinds of contexts:

A context is mostly a container for component instances. A component should be defined with a scope that describes in which context its instances will be created and managed. There are three available scopes:

The global context object can easily be retrieved from the Tide singleton:

var tideContext:Context = Tide.getInstance().getContext();
       

Conversation contexts can be retrieved by their identifier and are automatically created if they do not exist:

var tideContext:Context = Tide.getInstance().getContext("someConversationId");
       

Note however that this is not the recommended way of working with conversation contexts. See the Conversations section.

Components can be registered programmatically by any of the following methods:

  • Manual registration with Tide.getInstance().addComponent():
    Tide.getInstance().addComponent("myComponent", MyComponent):
                
    This method takes two main arguments: the component name and the component class.
  • It also has optional arguments that can be used to describe the metadata of the component:
    Tide.getInstance().addComponent(componentName, componentClass, inConversation, autoCreate, restrict);
                
    inConversation is a boolean value indicating whether the component in conversation-scoped (it is false by default), autoCreate is true by default and indicates that the component will be automatically instantiated by the container. Finally restrict is related to security and indicates that the component instance has to be destroyed when the user logs out from the application (so that its state cannot be accessed by unauthenticated users).
  • When necessary, it is possible to define initial values for some properties of the component instances with:
    Tide.getInstance().addComponentWithFactory("myComponent", MyComponent, { property1: value1, property2: value2 });
                
    Of course, this assumes that the component class has accessible setters for the properties specified in the initialization map. Values may be string expressions of the form #{component.property}, and are then evaluated at run time as a chain of properties starting from the specified contextuel component. All other values are assigned as is.

It is alternatively possible (and indeed recommended) to describe the metadata of the component with annotations in the component class. This simplifies the component registration and is often more readable.

Tide.getInstance().addComponents([MyComponent]);

[Name("myComponent")]
public class MyComponent {

    public MyComponent():void {
    }
}
       

Warning

A component class must have a default constructor.

Once a component is registered, you can get an instance of the component from the Context object by its name, for example tideContext.myComponent will return the unique instance of the component MyComponent that we have defined before.

You can also retrieve the instance of a component that extend a particular type with tideContext.byType(MyComponent). Of course it is more useful when specifying an interface so you can get its configured implementation: tideContext.byType(IMyComponent). When many implementations of an interface are expected to exist in the context, you can use tideContext.allByType(IMyComponent) to retrieve all of them.

Note

If no component has been registered with a particular name, tideContext.someName will by default return a client proxy for a remote service named someName. In particular tideContext.someName will return null only if a component named someName has been configured with the metadata autoCreate set to false.

When using dependency injection annotations ([In], [Out] and [Inject]) on component properties, Tide implicitly registers a component of the target type when it is a concrete class (not an interface):

[Name("myInjectedComponent")]
public class MyInjectedComponent {
	[In]
	public var myComponent:MyComponent;
}
       

Will implicity register a component of class MyComponent, even if you have never called Tide.addComponent() for this type.

Besides all these options for registering components, it is also possible to dynamically assign a component instance at any time in a Tide context with tideContext.myComponent = new MyComponent(). This allows you to precisely control the instantiation of the component and will implicitly register the corresponding component from the object class. For example you can use this feature to switch at runtime between different implementations of a component interface.

The last case is the one of UI components that are added and removed from the Flex stage. One of the things that Tide does at initialization time in the method initApplication() is registering listeners for the Flex events add and remove. On the add event, it automatically registers any component annotated with [Name] and puts its instance in the context. It also removes the instance from the context when getting the remove event.

Note that this behaviour can incur a significant performance penalty due to the ridiculously slow implementation of reflection in ActionScript so it can be disabled by Tide.getInstance().initApplication(false). You will then have to wire the UI components manually.

Once you have configured all components of the application, the Tide framework is able to inject the correct component instances for you anywhere you specify that you have a dependency by using one of the annotations [In] or [Inject].

The annotation [In] indicates a name-based injection point, meaning that Tide will assign the instance of the component with the specified name:

[Name("myInjectedComponent")]
public class MyInjectedComponent {

    [In("myComponent")]
    public var myComponent:IMyComponent;
    
}
	   

It is important to note that the injection in Tide is not done statically at instantiation time. It is implemented as a Flex data binding between the source tideContext.myComponent and the target myInjectedComponent.myComponent. That means that any change in the context instance is automatically propagated to all injected instances. For example if you assign manually a new instance to the context with tideContext.myComponent = new MyExtendedComponent(), the property myInjectedComponent.myComponent will be updated accordingly (assuming MyExtendedComponent implements IMyComponent, otherwise you will get a runtime exception).

In most cases, you can omit the name argument from the annotation and let Tide use the property name as a default. The previous example can be reduced to:

[In]
public var myComponent:IMyComponent;
       

You can also use property chain expressions of the form #{mySourceComponent.myProperty}:

[In("#{mySourceComponent.myProperty}")]
public var myComponent:IMyComponent;
       

Tide will then bind tideContext.mySourceComponent.myProperty to the target myInjectedComponent.myComponent.

Depending on the autoCreate metadata of the source component, Tide will automatically instantiate the component to bind it to the injection point. For components that are not auto created, you can force the instantiation at the injection point with:

[In(create="true")]
public var myComponent:IMyComponent;
       

This ensures that myComponent will never be null.

Tide also supports the concept of outjection, meaning that a component can publish some of its state to the context. This can be done with the annotation [Out], and just works in a similar way as injection by creating a data binding between the outjecting component and the context:

[Name("myOutjectingComponent")]
public class MyOutjectingComponent {

    [Bindable] [Out]
    public var myComponent:IMyComponent;
    
    public function doSomething():void {
        myComponent = new MyComponent();
    }
}
       

In this case, Tide will create a binding from myOutjectingComponent.myComponent to tideContext.myComponent. It is important that outjected properties are [Bindable] because this is how data binding is able to propagate the value to listeners. The method doSomething will change the value of myComponent in the context and also propagate it to all components having it in one of their injection points.

With server frameworks that support bijection (only Seam for now), you can also mark the outjection as remote, so Tide will also propagate the value to the server context. This requires that the value is serialized to the server and is thus used generally with entities or simple values (strings or numbers):

[Name("myOutjectingComponent")]
public class MyOutjectingComponent {

    [Bindable] [Out(remote="true")]
    public var myEntity:MyEntity;
    
    public function doSomething():void {
        myEntity = new MyEntity();
    }
}
       

Outjection is an interesting way of decoupling controllers and views. In our initial example, we could have used outjection instead of a typesafe model:



<?xml version="1.0" encoding="utf-8"?>
<mx:Panel
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*">
    
    <mx:Metadata>[Name]</mx:Metadata>

    <mx:Script>
        import com.myapp.events.HelloEvent;
        
        [Bindable] [In]
        public var message:String;
    </mx:Script>
    
    <mx:TextInput id="iname"/>
    <mx:Button label="Hello" click="dispatchEvent(new HelloEvent(iname.text))"/>
    <mx:Label id="lmessage" text="{message}"/>
</mx:Panel>
       
package com.myapp.controller {

    import org.granite.tide.events.TideResultEvent;
    import com.myapp.events.HelloEvent;
    import com.myapp.service.HelloService;

    [Name("helloController")]
    public class HelloController implements IHelloController {
        
        [Inject]
        public var helloService:HelloService;
        
        [Bindable] [Out]
        public var message:String;
        
        [Observer]
        public function hello(event:HelloEvent):void {
            helloService.hello(event.name, helloResult);
        }
        
        private function helloResult(event:TideResultEvent):void {
            this.message = event.result as String;
        }
    }
}
       

This is very convenient but note that it's relatively fragile and difficult to maintain as it is based on string names, and that you have to take care of name conflicts in the global context. Here you would have to ensure that no other component use the name message for another purpose. This problem can however be limited by defining proper naming conventions (for example with a prefix per module, or per use case).

Specifying an injection point with [In] is also based on string names and thus not typesafe. Alternatively you can (and should whenever possible) use the annotation [Inject] that specifies a type-based injection point. Tide will lookup any component that extend or implement the specified type and inject an instance of this component:

[Name("myInjectedComponent")]
public class MyInjectedComponent {

    [Inject]
    public var bla:IMyComponent;
    
}
       

Here no name is used, Tide uses only the target type IMyComponent to match with a registered component. If more than one component are matching, the result is undefined and the first registered component will be selected. It is thus recommended to register only one component for each interface used in injection points and to avoid too generic types in injection points (e.g. [Inject] public var bla:Object will generally not be very useful).

However it can be useful to register many component implementations for the same interface in the case of service registries. You can define a service interface, register many implementations, and then retrieve all registered implementations with tideContext.allByType(IMyService). This is for example how Tide handles exception converters of message interceptors internally.

You can also inject the context object to which the component belongs with either [In] or [Inject] by specifying the source type Context or BaseContext. This will always be a static injection because the context of a component instance cannot change.

[Inject]
public var myContext:Context;
       

Tide manages the lifecycle of the components (instantiation and destruction) and provides a means to react to these events with the annotations [PostConstruct] and [Destroy] than can be put on any public method without argument of the component and will be called by Tide on the corresponding events. [PostConstruct] is called after all injections and internal initializations have been done so it can been used to do some custom initialization of a component instance. [Destroy] can be used to cleanup used resources.

[Name("myComponent")]
public class MyComponent {

    [PostConstruct]
    public function init():void {
        // ...
    }
    
    [Destroy]
    public function cleanup():void {
        /// ...
    }
}
       

We have already seen in the previous section how the Tide context can server as a centralized bus to propagate events between managed components. The [In] and [Out] annotations were used to define a kind of publish/subscribe model for events of type PropertyChangeEvent.

However other kinds of events can be propagated though the event bus. Tide automatically registers itself as listener to managed events on all managed components, and forwards the events it receives to interested observers by matching the event with the observer definition.

Let's see in a first step what kind of events can be managed:

There are two ways of dispatching untyped events:

public function doSomething():void {
    dispatchEvent(new TideUIEvent("myEvent", arg1, { arg2: "value" }));
}
	   

TideUIEvent takes a variable list of arguments that will be propagated to all observers.

The following method is stricly equivalent and is a bit shorter if you already have an instance of the context somewhere:

public function doSomething():void {
    tideContext.raiseEvent"myEvent", arg1, { arg2: "value" });
}	  
        

Untyped events are very convenient but as said before they are matched by name (like normal Flex events) and thus are prone to typing errors when writing the name of the event in the observer. It is thus recommended when possible to define typed events. As Tide will match by the event class, the Flex compiler will immediately detect that a class name has been incorrectly typed.

There are two options to create custom typed events. First you can create an event class with the type TideUIEvent.TIDE_EVENT. Tide will always automatically listen to this type of events and there is no more configuration needed.

public class MyEvent extends Event {
    
    public var data:Object;
    
    public function MyEvent(data:Object):void {
        super(TideUIEvent.TIDE_EVENT, true, true);
        this.data = data ;
    }
}
       

You can also simply extend the existing AbstractTideEvent class:

public class MyEvent extends AbstractTideEvent {
    
    public var data:Object;
    
    public function MyEvent(data:Object):void {
        super();
        this.data = data ;
    }
}
       

Note that when creating custom event classes, you should set the bubbling and cancelable properties of the event to true:

Bubbling is necessary when you dispatch the event from UI components. It allows to declare only the top level UI components as Tide-managed components, and avoid the performance cost of managing all UI components. For example ItemRenderers can simply dispatch such events, they will be bubbled to their owning UI component and there received and handled by Tide, without Tide knowing anything of the item renderer itself.

Cancelable makes possible to call event.stopPropagation() to stop Tide from propagating the event further.

This first option is easy to use, but creates a compile-time dependency on the Tide framework (either extending AbstractTideEvent or using the type TIDE_EVENT). You can alternatively create any Flex custom event and then declare it as a managed event in all components that dispatch it.

public class MyEvent extends Event {
    
    public var data:Object;
    
    public function MyEvent(data:Object):void {
        super("myEvent", true, true);
        this.data = data ;
    }
}
       
[Name("myComponent")]
[ManagedEvent(name="myEvent")]
public class MyComponent extends EventDispatcher {
    
    public function doSomething():void {
        dispatchEvent(new MyEvent({ property: "value" }));
    }
}
       

Note that this second option is more vulnerable to typing errors because you have to write the event name in the [ManagedEvent] annotation and the Flex compiler does not enforce any control in the annotations.

Now that you know how to dispatch an event that Tide will be able to manage, let's see how to tell Tide what to do with this event. The key for this is the annotation [Observer] that can be put on any public method of a component and will be called when

Once again there are a few possibilities to observe events passed through the bus. For untyped events, you have to specify the name of the event you want to observe in the [Observer("myEvent")] annotation. The target observer method can either have a single argument of type TideContextEvent, or a list of arguments that will be set with the arguments of the source TideUIEvent:

[Observer("myEvent")]
public function eventHandler(event:TideContextEvent):void {
   // You can get the arguments from the events.params array
   var arg1:Object = event.params[0];
   var arg2:Object = event.params[1]["arg2"];
   ...
   // arg2 should be equal to "value"
}
       

Or

[Observer("myEvent")]
public function eventHandler(arg1:Object, arg2:Object):void {
    // arg2["arg2"] should be equals to "value"
}
       

One method can listen to more than one event type by specifying multiple [Observer] annotations:

[Observer("myEvent")]
[Observer("myOtherEvent")]
public function eventHandler(arg1:Object, arg2:Object):void {
    // arg2["arg2"] should be equals to "value"
}
       

Or by separating the event types with commas:

[Observer("myEvent, myOtherEvent")]
public function eventHandler(arg1:Object, arg2:Object):void {
    // arg2["arg2"] should be equals to "value"
}
       

Observers for typed events can have only one form:

[Observer]
public function eventHandler(event:MyEvent):void {
    // Do something
}
       

The match will always be done on the event class, so there is nothing to declare in the [Observer] annotation. Note that this is recommended to use this kind of typed events for coarse grained events in your application, otherwise this can lead to a proliferation of event classes. Future versions of Tide will allow for more specific matching on the handler method allowing the reuse of the same event class in different use cases.

There are other possibilities than the annotation [Observer] to register event observers:

If a component has registered an observer for an event and is not instantiated when the event is raised, it will be automatically instantiated, unless it is marked as [Name("myComponent", autoCreate="false")]. It is however possible to disable this automatic instantiation for a particular observer with [Observer("myEvent", create="false")]. In this case the target component instance will react to the event only if it already exists in the context.

Now you should be able to easily connect all parts of your application through events.

A conversation context shares its two main features with the global context: it is a container of component instances and propagates events between these component instances. It has two important differences:

It is important to note that all conversation contexts are completely isolated. A component instance in a conversation context can only receive events dispatched from another component instance in the same conversation context. Similarly when using injection or outjection, the injected instance will be in the same conversation context as the target component instance.

Another important thing is that conversation contexts are in fact considered as children of the global context. There are some visibility rules between a conversation context and the global context:

A conversation context can be simply created by Tide.getInstance().getContext("someConversationId"), however the recommended way to create a new conversation is to dispatch an event that implement IConversationEvent from the global context (or from a conversation context to create a nested conversation). The IConversationEvent has a conversationId property that will be used as id of the newly created conversation. The built-in TideUIConversationEvent can be used instead of TideUIEvent when using untyped events. If the conversation id is set to null, Tide will automatically assign an incremental numeric id to the new context.



<mx:List id="list" dataProvider="{customerRecords}" 
    change="dispatchEvent(new TideUIConversationEvent(list.selectedItem.id, "viewRecord", list.selectedItem))")/>
       
[Name("customerRecordController")]
public class CustomerRecordController {

    [Observer("viewRecord")]
    public function selectRecord(record:Record):void {
        // Start the conversation
        // For example create a view and display it somewhere
    }
}
       

A conversation context can be destroyed by tideContext.meta_end(). We'll see the use of the merge argument of this method later.

Here is a more complete example of end-to-end conversation handling by a controller:

[Name("customerRecordController", scope="conversation")]
public class CustomerRecordController {

    [In]
    public var mainTabNavigator:TabNavigator;
    
    [In(create="true")]
    public var recordView:RecordView;
    

    [Observer("viewRecord")]
    public function viewRecord(record:Record):void {
        recordView.record = record;
        mainTabNavigator.addChild(recordView);
    }
    
    [Observer("closeRecord")]
    public function closeRecord(event:TideContextEvent):void {
        mainTabNavigator.removeChild(recordView);
        event.context.meta_end();
    }
}
       

RecordView.mxml:

<mx:Panel label="Record #{record.id}">
    <mx:Metadata>[Name("recordView", scope="conversation")]</mx:Metadata>
    <mx:Script>
        [Bindable]
        public var record:Record;
    </mx:Script>
    
    <mx:Label text="{record.description}"/>
    
    <mx:Button label="Close" 
        click="dispatchEvent(new TideUIEvent('closeRecord'))"/>
</mx:Panel>
       

The use case is that we want to open a new tab to display a customer record when the user clicks on the customer in a list. Here is the process:

If the user clicks on many elements in the list, one tab will be created for each element.

The user can then click on the Close button that will trigger the closeRecord event. The controller will then remove the tab from the navigator and end the conversation context. meta_end() schedules the destruction of the context for the next frame, then all component instances of the context and the context itself are destroyed.

One of the main points of the Tide framework is that its concepts are completely integrated with the data management features. In particular each context holds its own entity cache so you can modify data in one conversation without touching the others. Only when the user decides to save its changes you can trigger the merge of the changes in the global context and its entity cache, and to the other conversation contexts.

Each context having its own entity cache has some implications:

Tide provides a few extension points that can be used to extend its functionality.

First there are four events that are dispatched on some internal events:

All these events can be observed from any component as standard Tide events:

[Name("myComponent")]
public class MyComponent {
    
    [Observe("org.granite.tide.startup")]
    public function startup():void {
        // Do some initialization stuff here...
    }
} 
	   

It is also possible to integrate a bit more deeply with the framework by implementing a plugin (the interface ITidePlugin). A plugin must be a singleton with a getInstance() method and implement a setter for the tide property. It can then register event listeners on the Tide instance itself. The type of the dispatched event is TidePluginEvent and it contains some parameters depending on the event in its map property params. The following events are dispatched:

Here is an example of a simple (and useless) plugin that traces the creation of all components annotated with [Trace]:

public class TideTrace implements ITidePlugin {
   
    private static var _tideTrace:TideTrace;
    
    
    public static function getInstance():TideTrace {
        if (!_tideTrace)
            _tideTrace = new TideTrace();            
        return _tideTrace;
    }
    
    public function set tide(tide:Tide):void {
        tide.addEventListener(Tide.PLUGIN_ADD_COMPONENT, addComponent);
    }
    
    private function addComponent(event:TidePluginEvent):void {
        var descriptor:ComponentDescriptor = event.params.descriptor as ComponentDescriptor;
        var type:Type = event.params.type as Type;
        var anno:Annotation = type.getAnnotationNoCache('Trace');
        if (anno != null)
            trace("Component added: " + descriptor.name);
    }
}
	   

There is not much Tide can do concerning security, however it is possible to declare that a particular component can exists only when the user is authenticated so its state cannot be accessed or modified from unauthorized users. You can use [Name("myComponent", restrict="true")] on a components to specify this.

Tide will then automatically clear all data of the restricted components when the user logs out and the session becomes anonymous.

If you have a big number of components to initialize, your main MXML application will quickly be polluted with lots of Tide initializations. This can be cleaned up by implementing a Tide initialization module class, which just has to implement ITideModule. Then you can use addModule to call the initialization of a whole application:

Tide.getInstance().addModule(MyModule);

public class MyModule implements ITideModule {
   public function init(tide:Tide):void {
       tide.addExceptionHandler(ValidationExceptionHandler);
       ...

       tide.addComponents([Component1, Component2]);
       tide.addComponent("comp3", Component3);
       ...
   }
}
        

You can think of it as a XML configuration file, such as Seam components.xml or Spring context.xml.

Using Tide modules is also necessary if you need to register components that are dynamically loaded from a Flex module. In this case, Tide will need to know the Flex ApplicationDomain to which the component classes belong, and you have to pass it to the Tide.addModule() method.

Here is an example on how to handle dynamic loading of Flex modules :

private var _moduleAppDomain:ApplicationDomain;

public function loadModule(path:String):void {
    var info:IModuleInfo = ModuleManager.getModule(path);
    info.addEventListener(ModuleEvent.READY, moduleReadyHandler, false, 0, true);
    _moduleAppDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
    info.load(appDomain);
}

private function moduleReadyHandler(event:ModuleEvent):void {
    var loadedModule:Object = event.module.factory.create();
    Tide.getInstance().addModule(loadedModule, _moduleAppDomain);
}
        

Alternatively you can also use the Flex MX or Spark ModuleLoader components, and just ensure that you are using a specific application domain when loading a module.



<mx:ModuleLoader id="moduleLoader"
    applicationDomain="{new ApplicationDomain(ApplicationDomain.currentDomain)}"
    ready="Tide.getInstance().addModule(moduleLoader.child, moduleLoader.applicationDomain)"/> 
        

You can then change the loaded module with this code :

private function changeModule(modulePath:String):void {
    if (moduleLoader.url != modulePath) {
        moduleLoader.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
        moduleLoader.unloadModule();
        moduleLoader.loadModule(modulePath);
    }
}
        

Flash/Flex provides an API to handle SEO friendly linking from the url of the swf. For example you may want to provide a simple url to access a particular resource : http://my.domain.com/shop/shop.html#product/display/tv. To have this working you have to generate the html wrapper with Flash Builder / Ant / Maven and use the html wrapper instead of accessing the swf directly. See for more details on Flex deep linking.

Tide provides a way to integrate deep linking with the MVC framework. It uses a technique inspired by JAX-RS so that changes in the browser url will trigger a method on a component. It first requires to enable the corresponding Tide plugin just after the Tide initialization with :

Tide.getInstance().initApplication();
Tide.getInstance().addPlugin(TideUrlMapping.getInstance()); 	      
 	      

You will also need to keep the annotation [Path] in your compilation options in Flash Builder / Ant / Maven.

Once enabled, the plugin will listen to browser url changes, and split the url after # in two parts. The part before the first slash will identify the target controller, and the part after the first slash will determine the target method. In the previous example, the controller has to be annotated with [Path("product")] and the method with [Path("display/tv")] :

[Name("productController")]
[Path("product")]
public class ProductController {

    [Path("display/tv")]
    public function displayTv():void {
        // Do something to display the TV...
    }
}
 	      

Of course you won't want to have a different method for each kind of resource so you can use placeholders that will match method arguments :

[Name("productController")]
[Path("product")]
public class ProductController {

    [Path("display/{0}")]
    public function display(productType:String):void {
        // Do something to display the product...
    }
}