-
Notifications
You must be signed in to change notification settings - Fork 3
Appendix B: CommandHandlers
Now that we've seen how we can associate Validators
with CommandHandlers
to control when they are available, we can now explain a little more about the mechanism behind mapping a Command
to a CommandHandler
. Generally speaking, you don't need to know what's going on behind the scenes, but a little knowledge can help shed some light on things if you run into problems.
So far we've only ever mapped a single CommandHandler
to a single Command
. For most behaviour this will serve your purpose. But every so often you will want to implement behaviour for a Command
that has a more generic quality to it.
For example, the CoreEditor
application defines a number of Commands
at core.editor.entities.Commands
. Here is a selection of some of these:
public static const REFRESH :String = "core.refresh";
public static const COPY :String = "core.copy";
public static const CUT :String = "core.cut";
public static const PASTE :String = "core.paste";
public static const DELETE :String = "core.delete";
public static const UNDO :String = "core.undo";
public static const REDO :String = "core.redo";
These are some reasonably generic commands. The CoreApp_Extensions
create Actions for these, and bind some of them to keyboard shortcuts (such as CTRL+Z for UNDO). However you can imagine that implementing the behaviour of COPY would require a different approach if you were copying image data from an ImageEditorContext
, compared to if you were copying text from a TextEditorContext
.
This is a situation where you would have multiple CommandHandler's
associated with the same Command
. One CommandHandler
would be responsible for copying image data, the other for copying text data. You would make sure that the CopyImageDataCommandHandler
is associated with a ContextValidator
that validates for the ImageEditorContext
, and the CopyTextDataCommandHander
is associated with a ContextValidator
that validates for a TextEditorContext
. Like so:
var commandHandlerFactory:CommandHandlerFactory = new CommandHandlerFactory( Commands.COPY, CopyImageDataCommandHandler );
commandHandlerFactory.validators.push( new ContextValidator( CoresEditor.contextManager, ImageEditorContext ) );
CoreApp.resourceManager.addResource( commandHandlerFactory );
commandHandlerFactory = new CommandHandlerFactory( Commands.COPY, CopyTextDataCommandHandler );
commandHandlerFactory.validators.push( new ContextValidator( CoreEditor.contextManager, TextEditorContext ) );
CoreApp.resourceManager.addResource( commandHandlerFactory );
At run-time, if only an ImageEditorContext
is available, then only the CopyImageDataCommandHandler
will be validated. So no problems there. The same is true if only the TextEditorContext
is available.
But what if both Contexts are available? This is a situation where the framework (specifically the CommandManager
) helps simplify things, and removes the need to write any 'special case' code to handle this situation.
When both contexts are available, both of the Validators will have their state set to true. If the COPY Command
is invoked, how does the framework choose which CommandHandler
to execute? This is where the IMetricValidator
interface comes in.
The IMetricValidator
interface can be found at core.appEx.core.validators
. Here it is:
package core.appEx.core.validators
{
public interface IMetricValidator extends IValidator
{
function get metric():Number
}
}
As you can see, there's not much to it. It extends IValidator
and exposes a single property 'metric'. This is a number between 0-1 that reflects just how well 'validated' this particular Validator is. What is meant by this?
The ContextValidator
class implements IMetricValidator
and returns a number based upon how closely it matches the Context. It will return a higher metric if the Context it is matching has been focused more recently. The CommandManager
can then use this metric to 'score' a CommandHandler
based on specificity. It will then execute the CommandHandler
with the best score.
The ContextValidator
also takes into account the 'specificity' of its match.
Given the following two ContextValidators
, which one is more closely matched if the current Context
is an ImageEditorContext
?
new ContextValidator( CoreEditor.contextManager, ImageEditorContext );
new ContextValidator( CoreEditor.contextManager, IVisualContext );
Hopefully you can intuitively see that both Validators
would have a state of 'true' (as both Contexts
implement IVisualContext
), but the first Validator
is definitely more 'specific'.
This is the mechanism by which the framework selects the most applicable CommandHandler
.