Skip to content
Robert Silverton edited this page Jul 3, 2014 · 5 revisions

View this tutorial's changes on GitHub.

If you're working with the tutorial repo, open a Git Shell in the CoreEditor-HelloWorld-Example-as folder and run the following command:

git checkout -f step-8

Otherwise, follow the instructions below.


Overview

In the previous tutorial we created a AddStringCommandHandler class. In its execute function we have the following code that does the work.

var context:StringListContext = CoreEditor.contextManager.getLatestContextOfType(StringListContext);
var length:int = context.dataProvider.length;
context.dataProvider.addItem("Item " + (length+1));

To recap - we get a reference to our StringListContext, and we add a new string to the end of its data provider.

In this tutorial we are going to see how with just a few lines of code, we can turn this work into an Operation, and get a bunch of cool features for free.


What are Operations?

An Operation is a class that is designed to do one particular task. Here is an example of a simple Operation that adds an item to a list.

package core.app.operations
{
	import core.data.ArrayCollection;
	
	import core.app.core.operations.IOperation;

	public class AddItemOperation implements IOperation
	{
		protected var item			:*;
		protected var list			:ArrayCollection;
		protected var index			:int;
		
		public function AddItemOperation( item:*, list:ArrayCollection, index:int = -1 )
		{
			this.item = item;
			this.list = list;
			this.index = index;
		}

		public function execute():void
		{
			if ( index == -1 || index >= list.length )
			{
				list.addItem( item );
			}
			else
			{
				list.addItemAt( item, index );
			}
		}
		
		public function get label():String
		{
			return "Add item";
		}
	}
}

It takes three items in its constructor, an item to be added, the list to be added to and an optional index in that list to insert the item at. When the Operation’s execute() function is called, it carries out the work of adding the item to the list.

At this point all this probably seems like a completely over-engineered approach to simply adding an item to a list. We’ve basically turned this code:

context.dataProvider.addItem("Item " + (length+1));

Into this:

var operation:AddItemOperation = new AddItemOperation("Item" + (length+1), context.dataProvider);
operation.execute();

Which hasn’t really simplified anything at all. In fact it’s double the amount of code we need to write. Why go to all this trouble?


IUndoableOperations

One word – Undo. It’s relatively straightforward to convert the AddItemOperation into being undoable. Like so:

package core.app.operations
{
	import core.data.ArrayCollection;
	
	import core.app.core.operations.IUndoableOperation;
	
	public class AddItemOperation implements IUndoableOperation
	{
		protected var item			:*;
		protected var list			:ArrayCollection;
		protected var index			:int;
		
		public function AddItemOperation( item:*, list:ArrayCollection, index:int = -1 )
		{
			this.item = item;
			this.list = list;
			this.index = index;
		}

		public function execute():void
		{
			if ( index == -1 || index >= list.length )
			{
				list.addItem( item );
			}
			else
			{
				list.addItemAt( item, index );
			}
		}
		
		public function undo():void
		{
			list.removeItemAt( list.getItemIndex( item ) );
		}
		
		public function get label():String
		{
			return "Add item";
		}
	}
}

All we’ve done is switch our interface from IOperation to IUndoableOperation operation and added an undo() function, and now our Operation is reversible. If we have a group of classes like this that perform work on our context, we can string them together one after another to form a history of operations for that context. Calling ‘execute()’ on each operation in the order they appear in the list will cause time to ‘move forward’. Conversely calling ‘undo()’ in the opposite order will do the opposite.

There is another class in the CoreApp framework called the OperationManager. This manager’s sole purpose is to maintain a single list of Operations, and provides an easy interface for logically manipulating a single history.

If we were to give our StringListContext an instance of an OperationManager, then any CommandHandlers making changes to the Context could wrap up their work in Operations, and add these little units to the Context’s own OperationManager. This would then serve as this particular Context’s undo history. Let’s do this now.


Replace the code in your StringListContext with the following:

package helloWorld.contexts
{
	import flash.display.DisplayObject;
	
	import core.appEx.core.contexts.IOperationManagerContext;
	import core.appEx.core.contexts.IVisualContext;
	import core.appEx.managers.OperationManager;
	import core.data.ArrayCollection;
	
	import helloWorld.ui.views.StringListView;
	
	public class StringListContext implements IVisualContext, IOperationManagerContext
	{
		private var _view			:StringListView;
		
		private var _dataProvider		:ArrayCollection;
		
		private var _operationManager	:OperationManager;
		
		public function StringListContext()
		{
			_view = new StringListView();
			
			_operationManager = new OperationManager();
			
			_dataProvider = new ArrayCollection();
			_view.dataProvider = _dataProvider;			
		}
		
		public function get view():DisplayObject
		{
			return _view;
		}
		
		public function dispose():void
		{
			_operationManager.dispose();
		}
		
		public function get dataProvider():ArrayCollection { return _dataProvider; }
		
		public function get operationManager():OperationManager { return _operationManager; }
	}
}

We’ve got a new private variable ‘_operationManager’ that we set with a new instance in the constructor. We also supply a read-only getter for the operationManager to comply with the IOperationManagerContext interface our Class is now implementing. We also make sure we call the OperationManager’s dispose() method during the Context’s own dispose() method. This ensures any operations in the OperationManager’s history are disposed and made eligible for garbage collection.

There is some code contributed by CoreApp_Extensions that will automatically map UNDO and REDO Commands to your Context's OperationManager's undo() and redo() methods. We’ll look at this in more detail later, but for now we’ve finished giving our Context a history.


Converting our CommandHandler to use Operations

Now that our Context supports the use of operations, let’s update our CommandHandler to take advantage of this. Replace the code in AddStringCommandHandler with the following:

package helloWorld.commandHandlers
{
	import core.app.operations.AddItemOperation;
	import core.appEx.core.commandHandlers.ICommandHandler;
	import core.editor.CoreEditor;
	
	import helloWorld.contexts.StringListContext;
	
	public class AddStringCommandHandler implements ICommandHandler
	{
		public function AddStringCommandHandler()
		{
		}
		
		public function execute(parameters:Object):void
		{
			var context:StringListContext = CoreEditor.contextManager.getLatestContextOfType(StringListContext);
			
			var item:String = "Item " + (context.dataProvider.length+1);
			var addItemOperation:AddItemOperation = new AddItemOperation( item, context.dataProvider );
			context.operationManager.addOperation(addItemOperation);
		}
	}
}

That last new line of code is the one we’re interested in. We’re accessing the Context’s operationManager reference and adding our Operation. By default, the OperationManager will automatically execute any Operations you add to it (you can pass ‘false’ as the second parameter if you want to do this manually).


Build & Run

Now build and run the application. Make sure your StringListView is open, then click your ADD_STRING Action a few times to add some items to the list. You should now be able to use Edit->Undo and Edit->Redo (or CTRL+Z and CTRL+Y) to undo and redo respectively.


< Previous | Next >