5 Decoupled Bindings

Introduced with version 2.3 this feature adds a major new option to the existing ways of decoupling the parts of your application, injection and messaging. It's not a new idea though. Similar concepts existed in Seam in the Java world (based on its @In and @Out annotations) and have already been picked up by other Flex frameworks, too, like in GraniteDS that also offers [In] and [Out] metadata configuration.

5.1 Comparing Dependency Injection and Decoupled Bindings

This section aims to give an overview over the differences between two framework features that share a few similarities and a few suggestions for how to determine which mechanism to use in a particular use case.

Note that there are other frameworks who even mix these two mechanisms into a single feature. However I think that it is better to have a clear distinction as there are significant differences in the pros and cons for both. It also makes the code easier to read when there are distinct tags for these capabilities.

Dependency Injection

For details see 4 Dependency Injection

Classic Dependency Injection (using Parsley's [Inject] tag) offers the following advantages over Decoupled Bindings:

Decoupled Bindings

Decoupled Binding (using Parsley's [Publish] and [Subscribe] tags) offers the following advantages over Dependency Injection:

Of course there is also a price to pay compared to injection:

5.2 Basic Usage

To set up a publisher on a managed object you can use the Publish metadata tag:

[Publish][Bindable]
public var selectedContact:Contact;

In Flex applications you also need to use the [Bindable] tag on the publishing side, as the implementation for Flex is based on the standard Flex Binding architecture. For Flash applications see the last section in this chapter.

As soon as you update such a publisher property, the value will be pushed to all matching subscribers in any other object in any Context:

[Subscribe]
public var selectedContact:Contact;

If you do not specify an object id, the matching subscribers will be determined by the type of the property, in this case Contact. Like with injection and messaging this works polymorphically. You can publish a Dog and a subscriber for Animal would also get updated.

Like with all other features it is best to stick with matching by type and avoid the use of string identifiers if possible. If you need to publish multiple objects of the same type, you can specify an id:

[Publish(objectId="selectedContact")][Bindable]
public var selectedContact:Contact;

Of course the subscribers must specify the corresponding id then, too. In some case you may also be able to use scopes to confine the area of the application an object is published to and avoid the use of an id this way.

You can also let a property act as a publisher and subscriber at the same time:

[PublishSubscribe][Bindable]
public var selectedContact:Contact;

Now the value will be updated when any other publisher updates, but still act as a publisher itself.

Finally, like most other tags these tags can be used in MXML and XML, too:

<Object type="{MyPublisher}">
    <Publish property="selectedContact"/>
    <!-- other tags -->
</Object>

5.3 Avoiding Conflicts

For the Decoupled Binding facility to work in a reliable and robust way, you have to make sure that your setup does not lead to ambuigities. The framework must know which publisher is "in charge" for a particular subscriber at any point in time. In general publishing to different scopes cannot lead to conflicts as the scopes are completely shielded from each other. But for any publishing within a single scope for the same type or id (depending on whether you use an id), the rules are as follows:

As you see having more than one [Publish] tag for the same type or id within a single scope is illegal. The same goes for combining [Publish] and [PublishSubscribe] for the same type or id. In those cases the framework would not be able to determine "who is in charge". This problem does not occur with multiple [PublishSubscribe] tags. Since those are subscribers, too, changing one of them will also update all the others, so that they are always in sync.

5.4 Using Scopes

Like with Messaging the Decoupled Binding facility allows to use scopes. Sometimes you don't want to publish an object globally, but instead use different instances in different areas of the application. For a general introduction see 6.10 Using Scopes in the messaging chapter.

In contrast to messaging there is one major difference in the default behaviour. With messaging the default for the sending side is to dispatch through all scopes available for the Context so that the receiving side can decide which scope to listen for. This is different for publishing as the former would lead to conflicts too often (see preceding section). Instead the default is to publish only to the global scope.

For any other scope than the global scope you have to use the scope attribute explicitly:

[Publish(scope="window")][Bindable]
public var selectedContact:Contact;

And of course on all subscribers, too:

[Subscribe(scope="window")]
public var selectedContact:Contact;

5.5 Publishing Managed Objects

In all preceding examples the state of the published object did not change. You can use either container-managed objects or any other objects not known by the container and publish it to subscribers. If it is unmanaged it would remain so. In some cases you might want to explicitly add the object to the Context dynamically only for the time it is being published. This way it can also participate in messaging and other container features. This has the same effect as using the Context API to create dynamic objects like demonstrated in 8.7 Dynamic Objects, just with the advantage that you do not have to talk to the framework API.

This is how it works, you just add the managed attribute to the Publish tag:

[Publish(managed="true")][Bindable]
public var selectedContact:Contact;

Now anytime you set this variable to a Contact instance, that instance gets added to the Context automatically. It remains managed until:

This also means that the lifecycle methods demarcated with [Init] and [Destroy] can be used on the published object to get notified when the object gets published and removed.

5.6 Persistent Properties

A published property can be marked as persistent. Immediately after the object gets instantiated it will then receive the value from the last time the application ran:

[PublishSubscribe(persistent="true",objectId="selectedContact")]
public var selectedContactId : String;

Under the hood Parsley uses Local SharedObjects for storing the values. But this is only the default implementation which can be swapped like almost everything in Parsley, like explained in the next section. But if you only use simple types like String or Number and only the global scope (which is the default) and are fine with using Local SharedObjects for persistence, then you can safely skip all remaining parts of this section. The persistent attribute as shown above is all you need to know in this case.

Custom Persistence without Local SharedObjects

If you want to persist values in a user session on the server side for example, you can provide your own implementation and read and write values to and from a remote service. The interface looks like this:

[Event(name="change", type="flash.event.Event")]

public interface PersistenceManager extends IEventDispatcher {
	
    function saveValue (scopeId:String, baseType:Class, id:String, value:Object) : void;
	
    function deleteValue (scopeId:String, baseType:Class, id:String) : void;
	
    function getValue (scopeId:String, baseType:Class, id:String) : Object;
	
}

The scopeId, baseType and id values can be used to uniquely identify a value. The id alone is not guaranteed to be unique. You can use these three values to build a unique key to send to the server. The scopeId will be explained later. A change event has to be dispatched by the implementation whenever it receives new values from the server (or wherever it gets its data from). This includes the values loaded on application startup. The change event should be dispatched once all initial values are available.

Such a custom service can be registered like this:

BootstrapDefaults.config.scopeExtensions
    .forType(PersistenceManager)
    .setImplementation(MyCustomPersistenceManager);

Persisting Complex Types

The framework only detects changes of the actual reference stored in the persistent property. This is how the Publish/Subscribe mechanism works in general. When a published object changes internally, there is no need to notify the subscribers (and besides no sensible way to do that) as they already have a reference to the modified object. For persistent properties this has an impact though, as it means that it works best for simple properties like String or Number out of the box. Because for complex types it would indeed be desired that internal changes get persisted, too, as the server (or peristent store in general) does not have a permament reference to the object like a normal, local subscriber in your application.

For detecting changes to complex types you'd have to code the knowledge about these types into a custom PersistenceManager implementation. The framework cannot easily know which changes to observe (and how), so it is best to leave that to the application developer. Future versions might add support for common types at least (like an ArrayCollection where it could listen to the CollectionEvents).

Using Scopes

Persisting values works without additional setup steps when using the global scope (which is the default). But when you are using multiple Contexts and want to have persistent values for each of them (through using the local scope or a custom scope), then you need to take care of re-creating the same set of scopes and Contexts when the application restarts. This is usually done in some sort of application-specific navigation logic that handles the scopeId that is always passed to the PersistenceManager. To guarantee the correct delivery of local persistent values the Contexts need to be created with the same id's. Normally you do not care for the id's of the scopes and only specify the name. But if required the id can be specified explicitly:

<parsley:ContextBuilder config="..."> 
    <parsley:Scope name="someScopeName" uuid="{idFromLastSession}"/>
</parsley:ContextBuilder>

While the name is not unqiue (you can have as many scopes with the same name as you want as long as they do not overlap), the id is indeed a unique identifier for a scope. This is the same value that gets then passed as the scopeId parameter to all the methods of the PersistenceManager. On the persistent property itself you still only have to use the name, as it would get quite convoluted if you'd need to pass the uuid around:

[PublishSubscribe(persistent="true", objectId="selectedContact", scope="someScopeName")]
public var selectedContactId : String;

The framework simply knows which id that particular instance of the someScopeName scope has.

For a general overview regarding scopes see 6.10 Using Scopes.

5.7 Bindings in Flash

The implementation for Flash avoids the use of the Flex Binding facility. This feature is fully functional without Flex, too, but it requires some additional work. In Flash applications you would leave out the [Bindable] tag. Instead the publisher has to manually signal that the value of the publisher property has changed:

private var _contact:Contact;

[Publish]
public function get selectedContact () : Contact {
    return _contact;
}

public function set selectedContact (value:Contact) : void {
    _contact = value;
    dispatchEvent(new Event(Event.CHANGE));
}

If a single class has multiple publisher properties you can alternatively optimize and specify different change event types for different properties:

private var _contact:Contact;

[Publish(changeEvent="contactChanged")]
public function get selectedContact () : Contact {
    return _contact;
}

public function set selectedContact (value:Contact) : void {
    _contact = value;
    dispatchEvent(new Event("contactChanged"));
}

Apart from the necessity to dispatch a change event, everything else described in the preceding sections of this chapter also applies to Flash applications.