thoughts
Command manager
2009-09-06
Building a JavaScript heavy front end application? Are you defining actions that are called from various places in the application? As a part of another action or as a response to events? Here is a command system to the rescue.
Defining Commands
Defining commands is easy, just define a function (or class method for that matter) and pass it with a name to the register static. For clarity throughout this article, I will also import the Command class.
var Command = com.rikkertkoppes.util.Command;
function foo(a,b,c) {
console.log('foo',a,b,c);
}
Command.register('foo',foo);
Now you might be concerned about the scope your method will be executed in. Therefore, the register static takes a third parameter for the execution scope. This defaults to window.
Invoking commands
There are three ways to execute a command, all of which are roughly the same:
Command.doCommand('foo arg1 arg2 arg3');Command('foo')(arg1,arg2,arg3);Command().foo(arg1,arg2,arg3);
The first method, via the doCommand static takes one string. This string will be split by whitespace. The first piece will be taken as a command name and the other pieces are passed as arguments. This is useful for setting up a command line interface for testing purposes or for receiving messages from the server.
The second method passes a command name to the Command object. When calling the Command object like that, it will return the command, in the correct scope. This command can then be executed. Optionally, a second scope parameter can be passed to the Command object, this will override any previously set scope.
The third method calls the Command object without any parameters. This will return an object literal containing all commands in their correct scope. These can then be executed as well. These second and third options can also be used to retrieve function references to be used as event handlers. For instance:
document.body.addEventListener('click',Command('foo'));
Command mapping
In practice, mostly when working with event handlers, the arguments the event returns hardly ever line up with the arguments your command expects. This can be resolved by defining mapping methods. A map will be doing the rewriting of arguments.
foo.maps = {
event: function(e) {
return ['click: ',e.clientX,e.clientY];
}
};
window.addEventListener('click',Command.map('foo').event,false);
Notice that a maps object literal is added to the command method. This property contains any number of mapping functions that take the arguments given by the event handler and returns an array of arguments that will be passed to the command method. In this example, an ordinary event object is passed in and the mouse positions are returned. With this information, the command method is called.
Also notice that the method that will be added as an event listener is somewhat different. By calling the map static, a map object is returned, which is built from the original maps property you defined. This map object contains (new) method references that can be assigned as handlers.
Enabling and disabling commands
In certain cases you want to make it impossible for commands to be executed. When commands are hooked up to several interface elements (eg buttons and keyboard shortcuts), this is best done in a central place, hence:
Command.disable('foo')Command.enable('foo')
Attaching events
Since I wanted to make this command manager framework agnostic, it does not fire custom events. However, I added the following abstract static methods that you can implement yourself:
Command.onCommandEnable(name)Command.onCommandDisable(name)Command.onCommandExecute(name)
You can either put a bunch of code in these methods, or fire a custom event that other parts can listen to. If either of these implemented methods return false, the associated default action will not be executed.
Commands and use cases
Commands are designed to be the JavaScript analogue to use cases. These are the things that your application should do. One use cases could for example be "create a blogpost". For creating a blogpost, some user input data is needed, an email address and a post body, say. This data, and only this data, is the input for the command. The command should handle the rest.
Commands in a controller
Basically, a controller is the thing that responds to events and updates the view with information from a model. In the very basic sense, events are "things that happen". For server side controllers, events are usually http requests. For JavaScript controllers, events are DOM or custom events.
The most basic view in a JavaScript application is the DOM and its associated rendering on screen itself. More advanced applications use more elaborate components like dialogs, tab panels etc. These are also generally known as "widgets". These widgets can often fire custom events to which the controller can respond.
A model in the most basic sense is just a variable, but this can be expanded to complete object oriented structure. A (very) basic example of a MVC setup is the following:
//model
var message = 'Hello World';
//controller logic
// respond to event
window.onclick = function() {
//update the view
document.body.innerHTML = message;
}
When you have a lot of events and a lot of actions to do, some tidying up is needed. Usually, one already extracts methods that are used often. This is where commands come into play.
//model
var message = 'Hello World';
var controller = {}
controller.init = function() {
Command.register('hw',controller.hw,document.body);
window.addEventListener('click',Command('hw'),false);
}
controller.hw = function() {
document.body.innerHTML = message;
}
Get it
<script type="text/javascript" src="http://www.rikkertkoppes.com/modules/util/com.rikkertkoppes.util.Command.js"></script>- The source
- Example page (uses firebug console)
Additional resources (top 15)
Below is a list of additional resources that might contain extra information about the subject at hand. These are all sites linking to this one (i.e. backtracking).
