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.
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:
<DynamicObject>
tag in the Context) as they could
be removed anytime. [Init]
gets invoked.
This often simplifies internal initialization code. 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:
[Init]
gets invoked
on the object as the value can be published and updated at any point in time. 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>
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:
[Publish]
tag and any number of matching [Subscribe]
tags. [PublishSubscribe]
tags and any number of matching [Subscribe]
tags. [Publish]
tag without any matching [Subscribe]
tag and vice versa. If a
[Subscribe]
tag has no matching [Publish]
tag its value will be set to null. 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.
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;
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.
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.
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.