SlideShare ist ein Scribd-Unternehmen logo
1 von 7
Document prepared by Steve Widom of Exparency, LLC.
Implementing the Memento
Pattern in the Exparency BPM
System
Steve Widom, June, 2016
Introduction
The front end to the Exparency BPM (Business Process Management) system is a drawing tool in the
spirit of 2D CAD (schematic entry systems in the electrical world, ERD diagrams in the data modeling
world, etc.) The graphical primitives are shapes such as rectangles, circles, lines and text. What
differentiates the graphics from a Visio diagram, for example, is the fact that these geometries have
semantic interpretation in the sense that the shapes are connected to each other and these shapes and
connections have meaning.
Although drawing tools, or any other PC or Mac application for that matter, have CTRL-Z and CTRL-Y
functionality out of the box, it is rare to find that kind of functionality on a SaaS application. It is a
requirement of the Exparency BPM system to provide that familiar functionality and the purpose of this
document is to explain the architecture behind how this is achieved.
We will conclude the document by discussing a fortunate consequence of implementing the Memento
pattern – namely, adding Macro functionality that can be stored and replayed, while also being used as
a debugging tool for customer cases.
The Memento Pattern
CTRL-Z (oftentimes known as edit undo) and CTRL-Y (oftentimes known as edit redo) are the tenets of
the Memento Pattern. This pattern is well documented in the Gang of Four famous treatise that
exploded the whole Design Pattern industry in computer science and is diagramed right from that
source below:
Document prepared by Steve Widom of Exparency, LLC.
In a nutshell the entity undergoing change (the Originator) carries its state while the Memento object
saves the state at various points in time. It is the responsibility of the Caretaker to define those points
in time. For example, we are typically familiar with editing a text field. The Caretaker does not save
the state of that text field upon every character entered. It will, however, save that state when the
user hits enter or when the field loses focus.
While this is all well and good, the author tends to look at the edit undo/redo functionality slightly
differently as shown below.
Very simply, two queues can be easily implemented to store commands and their undoing. As the user
enters new commands they are placed on top of the Command queue as a LIFO (Last in/First out).
When the user issues an edit undo (CTRL-Z) the last entered command is popped off of the Command
queue and then it is placed on the Undo queue which is also configured as a LIFO. Similarly when the
user issues a redo (CTRL-Y) the last entered command is popped off of the Undo queue and then it is
placed back onto the Command queue.
The elegance and simplicity of this diagram hides fundamental features that are going on behind the
scenes:
1. When a command is added to the Command queue, the old state and the new state need to be
placed into that command object.
2. When an undo request is made the application must recover the old state stored in the
command object that was popped off of the Command queue and placed onto the Undo queue
– thus achieving the desired inverse effect (i.e. undo.)
The following sequence diagram represents this course of events:
Document prepared by Steve Widom of Exparency, LLC.
Notice that we have introduced a third actor into the mix – the Command Manager which encapsulates
the management of the two queues.
Implementation
The Command Manager is a modified version of Javascript code, originally written by Alexander Brevig
and available on GitHub. This modification is simply to use the HTML5 localStorage to store all
commands, in addition to being added to the managed command queue. There are several advantages
to this, the main one being persistence of a session of activity on the local client that can be restored
at any time without the penalty of a network hop and a database lookup. We will talk more about this
functionality towards the end of this document.
The Javascript code for this module is shown below:
var CommandManager = (function () {
function CommandManager() {}
CommandManager.executed = [];
CommandManager.unexecuted = [];
CommandManager.execute = function execute(globalService, cmd) {
cmd.execute(globalService);
CommandManager.executed.push(cmd);
};
CommandManager.undo = function undo(globalService) {
var cmd1 = CommandManager.executed.pop();
if (cmd1 !== undefined){
if (cmd1.unexecute !== undefined){
cmd1.unexecute(globalService);
}
CommandManager.unexecuted.push(cmd1);
Document prepared by Steve Widom of Exparency, LLC.
}
};
CommandManager.redo = function redo(globalService) {
var cmd2 = CommandManager.unexecuted.pop();
if (cmd2 === undefined){
cmd2 = CommandManager.executed.pop();
// This is not a code error. We are executing the last command twice because there is
// no command found on the CTRL-Y (undo) stack. Apparently this is conventional, and
// we are going to execute the last command one more time, which means we need to push
// it to the command stack twice since that is what we are doing.
CommandManager.executed.push(cmd2);
CommandManager.executed.push(cmd2);
}
if (cmd2 !== undefined){
cmd2.execute(globalService);
CommandManager.executed.push(cmd2);
}
};
CommandManager.saveCommandToLocalStorageCommandQueue = function(cmd) {
var currentCommandQueue = localStorage.getItem('CommandQueue');
if (currentCommandQueue == null) {
currentCommandQueue = [];
}
else {
currentCommandQueue = JSON.parse(currentCommandQueue);
}
var serialized = cmd.serialize();
currentCommandQueue.push(serialized);
var json = JSON.stringify(currentCommandQueue);
localStorage.setItem('CommandQueue', json);
};
CommandManager.getCommandsFromLocalStorageCommandQueue = function() {
var currentCommandQueue = localStorage.getItem('CommandQueue');
if (currentCommandQueue == null) {
currentCommandQueue = [];
}
else {
currentCommandQueue = JSON.parse(currentCommandQueue);
}
return currentCommandQueue;
};
return CommandManager;
})();
Notice that the Command Manager is implemented as a Singleton (yet another design pattern.) There
are a couple of points to be made about this code snippet. The first is that localStorage can only hold
string name/value pairs. Therefore when we add new commands to localStorage for future use, we
Document prepared by Steve Widom of Exparency, LLC.
deserialize the entire command array string representation back to a Javascript array, add the new
command to this array, and then re-serialize that object back to a string and store that string in
localStorage. Admittedly this can get expensive as the volume of commands entered gets large and in
a future version a different methodology will probably be employed. Given that the intent is to store
these commands in a backend database (perhaps a MongoDB document) as a Macro definition, it may
end up being a moot point anyway.
At the bottom of the class hierarchy is the command object which is a Javascript function that carries
all of the data that is needed to execute a command as well as undo the effects of a command. We
will use a swimlane as an example (in workflow systems it is common to group tasks assigned to a role
by physically placing those tasks in what is called a swimlane):
function EditSwimlaneCommand (data) {
this.oldSwimlaneName = data.oldSwimlaneName;
this.newSwimlaneName = data.newSwimlaneName;
this.oldFillColor = data.oldFillColor;
this.newFillColor = data.newFillColor;
this.oldOpacity = data.oldOpacity;
this.newOpacity = data.newOpacity;
};
EditSwimlaneCommand.prototype = {
constructor: EditSwimlaneCommand,
init: function() {
},
execute: function(globalService) {
globalService.executeEditSwimlaneCommand(this);
},
unexecute: function(globalService) {
globalService.unexecuteEditSwimlaneCommand(this);
},
serialize: function() {
return JSON.stringify({
objectType: "EditSwimlaneCommand",
data:{
oldSwimlaneName: this.oldSwimlaneName,
newSwimlaneName: this.newSwimlaneName,
oldFillColor: this.oldFillColor.toCSS(true),
newFillColor: this.newFillColor.toCSS(true),
oldOpacity: this.oldOpacity,
newOpacity: this.newOpacity
}
});
},
deserialize: function(jsonData) {
var data = JSON.parse(jsonData);
var cmd = new EditSwimlaneCommand(data.data);
return cmd;
}
Document prepared by Steve Widom of Exparency, LLC.
};
Notice that this Javascript object carries two sets of data – the new state as well as the old state. It
would not be possible to undo this command without knowledge of the old state. Also notice that this
object has the responsibility for serializing itself so that it can be placed on the respective queues as
well as localStorage as a string, as well as restoring itself from such a string representation.
Finally, notice that the an AngularJS service is engaged to perform the actual work on behalf of the
application to execute the command and to undo the command (via a call to the object’s unexecute
method.) This service is generally global to the entire application and thus is named as such
(globalService) and it has various methods for interacting with the various application controllers as
well as executing the REST API services. Being over 600 lines of Javascript AngularJS code, it is beyond
the scope of this document to show all that detail. Suffice it to say that methods related to pulling
drawing data from the backend MySQL database (fill colors, border widths and colors, coordinate data,
widths, heights, etc.) along with the other expected CRUD operations are the responsibility of this
service. Since this document is focused on the implementation of the Memento Pattern it is not
necessary to show all that detail.
We can now summarize our implementation pattern for achieving our goal of supporting CTRL-Z and
CTRL-Y:
1. For each command in the application create a command object such as the one shown above.
2. In the application controller add code that populates this command object with the new state
as well as the original state. The following code snippet is an example that relates to the
swimlane example just mentioned. Note that it uses Google’s Material UI Dialog to capture the
new swimlane data (name, fill color and opacity).
function EditSwimlaneDialogController($scope, $mdDialog, data) {
var self = this;
self.fillColor = data.swimlane.children[0].fillColor.toCSS(true);
self.opacity = data.swimlane.children[0].opacity;
self.name = data.swimlane.swimlaneName;
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.submitEditSwimlaneForm = function() {
var json = {
oldSwimlaneName: data.swimlane.swimlaneName,
newSwimlaneName: self.name,
oldFillColor:
data.swimlane.children[0].fillColor.toCSS(true),
newFillColor: self.fillColor,
oldOpacity: data.swimlane.children[0].opacity,
newOpacity: self.opacity
};
data.globalService.executeEditSwimlaneCommand(new
EditSwimlaneCommand(json));
$mdDialog.hide();
};
}
Document prepared by Steve Widom of Exparency, LLC.
3. Create a method in the globalService service that executes and unexecutes this command
object (possibly making asynchronous REST calls.) Going back to the same example of the
swimlane, here is a code snippet for the implementation of the
globalService.executeEditSwimlaneCommand() method:
executeEditSwimlaneCommand: function(cmd) {
this.pushCommand(cmd);
var swimlane = this.getDrawing().getSwimlane(this, cmd.oldSwimlaneName);
swimlane.fillColor = cmd.newFillColor;
swimlane.opacity = cmd.newOpacity;
this.getDrawing().changeSwimlaneName(this, swimlane,
cmd.newSwimlaneName);
this.getDrawing().refreshView(this);
},
Finally, the pushCommand method does the work of interacting with the Command Manager:
pushCommand: function(cmd) {
CommandManager.executed.push(cmd);
if (this.getMacroRecordState() == 'Record') {
CommandManager.saveCommandToLocalStorageCommandQueue(cmd);
}
},
Notice that this last code snippet has a logical test to determine whether the application is in Record
mode. We only want to store commands in localStorage if we are, indeed, recording these events.
A Word About Macros
In the Introduction of this document it was mentioned that a positive side effect of the implementation
of the Memento Pattern was the ability to record and playback macros that are stored in localStorage.
Long term the intent is to store these collections of commands as named macros in the back end
database. This should be a significant benefit to the user.
Additionally, however, there is a somewhat “selfish” reason for adding this functionality and that is
related to customer incidence reporting of a bug or general problem. Oftentimes a CSR or developer,
upon initiating a conversation with the customer, will naturally ask what they were doing at the time
that the bug or problem occurred. More likely than not the customer will not have those exact details
(why would they?) However, if the CSR or developer asks that the customer send them the command
queue stored in localStorage (effectively an event log with all the necessary detail) then the developer
can likely reproduce the problem in a development environment with a debugger and all. This will
hopefully turn out to be a very useful tool in diagnosing customer complaints. Only time will tell.

Weitere ähnliche Inhalte

Was ist angesagt?

Effective Spring Transaction Management
Effective Spring Transaction ManagementEffective Spring Transaction Management
Effective Spring Transaction Management
UMA MAHESWARI
 
Object Design - Part 1
Object Design - Part 1Object Design - Part 1
Object Design - Part 1
Dhaval Dalal
 
A Dictionary Of Vb .Net
A Dictionary Of Vb .NetA Dictionary Of Vb .Net
A Dictionary Of Vb .Net
LiquidHub
 

Was ist angesagt? (15)

Command pattern
Command patternCommand pattern
Command pattern
 
Effective Spring Transaction Management
Effective Spring Transaction ManagementEffective Spring Transaction Management
Effective Spring Transaction Management
 
Dive into SObjectizer 5.5. Third part. Coops
Dive into SObjectizer 5.5. Third part. CoopsDive into SObjectizer 5.5. Third part. Coops
Dive into SObjectizer 5.5. Third part. Coops
 
Object Design - Part 1
Object Design - Part 1Object Design - Part 1
Object Design - Part 1
 
Command Pattern
Command PatternCommand Pattern
Command Pattern
 
Refactoring & Restructuring - Improving the Code and Structure of Software
Refactoring & Restructuring - Improving the Code and Structure of SoftwareRefactoring & Restructuring - Improving the Code and Structure of Software
Refactoring & Restructuring - Improving the Code and Structure of Software
 
Meetup - Getting Started with MVVM Light for WPF - 11 may 2019
Meetup  - Getting Started with MVVM Light for WPF - 11 may 2019Meetup  - Getting Started with MVVM Light for WPF - 11 may 2019
Meetup - Getting Started with MVVM Light for WPF - 11 may 2019
 
Chain of Responsibility Pattern
Chain of Responsibility PatternChain of Responsibility Pattern
Chain of Responsibility Pattern
 
Managing Unstructured Data: Lobs in the World of JSON
Managing Unstructured Data: Lobs in the World of JSONManaging Unstructured Data: Lobs in the World of JSON
Managing Unstructured Data: Lobs in the World of JSON
 
Dive into SObjectizer 5.5. Ninth Part: Message Chains
Dive into SObjectizer 5.5. Ninth Part: Message ChainsDive into SObjectizer 5.5. Ninth Part: Message Chains
Dive into SObjectizer 5.5. Ninth Part: Message Chains
 
Dive into SObjectizer-5.5. Sixth part: Synchronous Interaction
Dive into SObjectizer-5.5. Sixth part: Synchronous InteractionDive into SObjectizer-5.5. Sixth part: Synchronous Interaction
Dive into SObjectizer-5.5. Sixth part: Synchronous Interaction
 
JEE.next()
JEE.next()JEE.next()
JEE.next()
 
Practical QML - Key Navigation, Dynamic Language and Theme Change
Practical QML - Key Navigation, Dynamic Language and Theme ChangePractical QML - Key Navigation, Dynamic Language and Theme Change
Practical QML - Key Navigation, Dynamic Language and Theme Change
 
Workshop 20: ReactJS Part II Flux Pattern & Redux
Workshop 20: ReactJS Part II Flux Pattern & ReduxWorkshop 20: ReactJS Part II Flux Pattern & Redux
Workshop 20: ReactJS Part II Flux Pattern & Redux
 
A Dictionary Of Vb .Net
A Dictionary Of Vb .NetA Dictionary Of Vb .Net
A Dictionary Of Vb .Net
 

Ähnlich wie Memento Pattern Implementation

Nt1310 Unit 3 Language Analysis
Nt1310 Unit 3 Language AnalysisNt1310 Unit 3 Language Analysis
Nt1310 Unit 3 Language Analysis
Nicole Gomez
 
Angular js meetup
Angular js meetupAngular js meetup
Angular js meetup
Anton Kropp
 
Buffer overflow tutorial
Buffer overflow tutorialBuffer overflow tutorial
Buffer overflow tutorial
hughpearse
 
379008-rc217-functionalprogramming
379008-rc217-functionalprogramming379008-rc217-functionalprogramming
379008-rc217-functionalprogramming
Luis Atencio
 
Jwis2011 ruo ando
Jwis2011 ruo andoJwis2011 ruo ando
Jwis2011 ruo ando
Ruo Ando
 
Big data unit iv and v lecture notes qb model exam
Big data unit iv and v lecture notes   qb model examBig data unit iv and v lecture notes   qb model exam
Big data unit iv and v lecture notes qb model exam
Indhujeni
 
Beginning MEAN Stack
Beginning MEAN StackBeginning MEAN Stack
Beginning MEAN Stack
Rob Davarnia
 

Ähnlich wie Memento Pattern Implementation (20)

MarGotAspect - An AspectC++ code generator for the mARGOt framework
MarGotAspect - An AspectC++ code generator for the mARGOt frameworkMarGotAspect - An AspectC++ code generator for the mARGOt framework
MarGotAspect - An AspectC++ code generator for the mARGOt framework
 
Nt1310 Unit 3 Language Analysis
Nt1310 Unit 3 Language AnalysisNt1310 Unit 3 Language Analysis
Nt1310 Unit 3 Language Analysis
 
DDD Framework for Java: JdonFramework
DDD Framework for Java: JdonFrameworkDDD Framework for Java: JdonFramework
DDD Framework for Java: JdonFramework
 
Angular js meetup
Angular js meetupAngular js meetup
Angular js meetup
 
Top 20 Asp.net interview Question and answers
Top 20 Asp.net interview Question and answersTop 20 Asp.net interview Question and answers
Top 20 Asp.net interview Question and answers
 
Functional programming in TypeScript
Functional programming in TypeScriptFunctional programming in TypeScript
Functional programming in TypeScript
 
Roboconf Detailed Presentation
Roboconf Detailed PresentationRoboconf Detailed Presentation
Roboconf Detailed Presentation
 
Buffer overflow tutorial
Buffer overflow tutorialBuffer overflow tutorial
Buffer overflow tutorial
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
 
379008-rc217-functionalprogramming
379008-rc217-functionalprogramming379008-rc217-functionalprogramming
379008-rc217-functionalprogramming
 
Jwis2011 ruo ando
Jwis2011 ruo andoJwis2011 ruo ando
Jwis2011 ruo ando
 
Javascripts hidden treasures BY - https://geekyants.com/
Javascripts hidden treasures            BY  -  https://geekyants.com/Javascripts hidden treasures            BY  -  https://geekyants.com/
Javascripts hidden treasures BY - https://geekyants.com/
 
A sane approach to microservices
A sane approach to microservicesA sane approach to microservices
A sane approach to microservices
 
Big data unit iv and v lecture notes qb model exam
Big data unit iv and v lecture notes   qb model examBig data unit iv and v lecture notes   qb model exam
Big data unit iv and v lecture notes qb model exam
 
Advanced Node.JS Meetup
Advanced Node.JS MeetupAdvanced Node.JS Meetup
Advanced Node.JS Meetup
 
Progscon 2017: Taming the wild fronteer - Adventures in Clojurescript
Progscon 2017: Taming the wild fronteer - Adventures in ClojurescriptProgscon 2017: Taming the wild fronteer - Adventures in Clojurescript
Progscon 2017: Taming the wild fronteer - Adventures in Clojurescript
 
Beginning MEAN Stack
Beginning MEAN StackBeginning MEAN Stack
Beginning MEAN Stack
 
Project description2012
Project description2012Project description2012
Project description2012
 
maXbox Starter 45 Robotics
maXbox Starter 45 RoboticsmaXbox Starter 45 Robotics
maXbox Starter 45 Robotics
 
SamzaSQL QCon'16 presentation
SamzaSQL QCon'16 presentationSamzaSQL QCon'16 presentation
SamzaSQL QCon'16 presentation
 

Memento Pattern Implementation

  • 1. Document prepared by Steve Widom of Exparency, LLC. Implementing the Memento Pattern in the Exparency BPM System Steve Widom, June, 2016 Introduction The front end to the Exparency BPM (Business Process Management) system is a drawing tool in the spirit of 2D CAD (schematic entry systems in the electrical world, ERD diagrams in the data modeling world, etc.) The graphical primitives are shapes such as rectangles, circles, lines and text. What differentiates the graphics from a Visio diagram, for example, is the fact that these geometries have semantic interpretation in the sense that the shapes are connected to each other and these shapes and connections have meaning. Although drawing tools, or any other PC or Mac application for that matter, have CTRL-Z and CTRL-Y functionality out of the box, it is rare to find that kind of functionality on a SaaS application. It is a requirement of the Exparency BPM system to provide that familiar functionality and the purpose of this document is to explain the architecture behind how this is achieved. We will conclude the document by discussing a fortunate consequence of implementing the Memento pattern – namely, adding Macro functionality that can be stored and replayed, while also being used as a debugging tool for customer cases. The Memento Pattern CTRL-Z (oftentimes known as edit undo) and CTRL-Y (oftentimes known as edit redo) are the tenets of the Memento Pattern. This pattern is well documented in the Gang of Four famous treatise that exploded the whole Design Pattern industry in computer science and is diagramed right from that source below:
  • 2. Document prepared by Steve Widom of Exparency, LLC. In a nutshell the entity undergoing change (the Originator) carries its state while the Memento object saves the state at various points in time. It is the responsibility of the Caretaker to define those points in time. For example, we are typically familiar with editing a text field. The Caretaker does not save the state of that text field upon every character entered. It will, however, save that state when the user hits enter or when the field loses focus. While this is all well and good, the author tends to look at the edit undo/redo functionality slightly differently as shown below. Very simply, two queues can be easily implemented to store commands and their undoing. As the user enters new commands they are placed on top of the Command queue as a LIFO (Last in/First out). When the user issues an edit undo (CTRL-Z) the last entered command is popped off of the Command queue and then it is placed on the Undo queue which is also configured as a LIFO. Similarly when the user issues a redo (CTRL-Y) the last entered command is popped off of the Undo queue and then it is placed back onto the Command queue. The elegance and simplicity of this diagram hides fundamental features that are going on behind the scenes: 1. When a command is added to the Command queue, the old state and the new state need to be placed into that command object. 2. When an undo request is made the application must recover the old state stored in the command object that was popped off of the Command queue and placed onto the Undo queue – thus achieving the desired inverse effect (i.e. undo.) The following sequence diagram represents this course of events:
  • 3. Document prepared by Steve Widom of Exparency, LLC. Notice that we have introduced a third actor into the mix – the Command Manager which encapsulates the management of the two queues. Implementation The Command Manager is a modified version of Javascript code, originally written by Alexander Brevig and available on GitHub. This modification is simply to use the HTML5 localStorage to store all commands, in addition to being added to the managed command queue. There are several advantages to this, the main one being persistence of a session of activity on the local client that can be restored at any time without the penalty of a network hop and a database lookup. We will talk more about this functionality towards the end of this document. The Javascript code for this module is shown below: var CommandManager = (function () { function CommandManager() {} CommandManager.executed = []; CommandManager.unexecuted = []; CommandManager.execute = function execute(globalService, cmd) { cmd.execute(globalService); CommandManager.executed.push(cmd); }; CommandManager.undo = function undo(globalService) { var cmd1 = CommandManager.executed.pop(); if (cmd1 !== undefined){ if (cmd1.unexecute !== undefined){ cmd1.unexecute(globalService); } CommandManager.unexecuted.push(cmd1);
  • 4. Document prepared by Steve Widom of Exparency, LLC. } }; CommandManager.redo = function redo(globalService) { var cmd2 = CommandManager.unexecuted.pop(); if (cmd2 === undefined){ cmd2 = CommandManager.executed.pop(); // This is not a code error. We are executing the last command twice because there is // no command found on the CTRL-Y (undo) stack. Apparently this is conventional, and // we are going to execute the last command one more time, which means we need to push // it to the command stack twice since that is what we are doing. CommandManager.executed.push(cmd2); CommandManager.executed.push(cmd2); } if (cmd2 !== undefined){ cmd2.execute(globalService); CommandManager.executed.push(cmd2); } }; CommandManager.saveCommandToLocalStorageCommandQueue = function(cmd) { var currentCommandQueue = localStorage.getItem('CommandQueue'); if (currentCommandQueue == null) { currentCommandQueue = []; } else { currentCommandQueue = JSON.parse(currentCommandQueue); } var serialized = cmd.serialize(); currentCommandQueue.push(serialized); var json = JSON.stringify(currentCommandQueue); localStorage.setItem('CommandQueue', json); }; CommandManager.getCommandsFromLocalStorageCommandQueue = function() { var currentCommandQueue = localStorage.getItem('CommandQueue'); if (currentCommandQueue == null) { currentCommandQueue = []; } else { currentCommandQueue = JSON.parse(currentCommandQueue); } return currentCommandQueue; }; return CommandManager; })(); Notice that the Command Manager is implemented as a Singleton (yet another design pattern.) There are a couple of points to be made about this code snippet. The first is that localStorage can only hold string name/value pairs. Therefore when we add new commands to localStorage for future use, we
  • 5. Document prepared by Steve Widom of Exparency, LLC. deserialize the entire command array string representation back to a Javascript array, add the new command to this array, and then re-serialize that object back to a string and store that string in localStorage. Admittedly this can get expensive as the volume of commands entered gets large and in a future version a different methodology will probably be employed. Given that the intent is to store these commands in a backend database (perhaps a MongoDB document) as a Macro definition, it may end up being a moot point anyway. At the bottom of the class hierarchy is the command object which is a Javascript function that carries all of the data that is needed to execute a command as well as undo the effects of a command. We will use a swimlane as an example (in workflow systems it is common to group tasks assigned to a role by physically placing those tasks in what is called a swimlane): function EditSwimlaneCommand (data) { this.oldSwimlaneName = data.oldSwimlaneName; this.newSwimlaneName = data.newSwimlaneName; this.oldFillColor = data.oldFillColor; this.newFillColor = data.newFillColor; this.oldOpacity = data.oldOpacity; this.newOpacity = data.newOpacity; }; EditSwimlaneCommand.prototype = { constructor: EditSwimlaneCommand, init: function() { }, execute: function(globalService) { globalService.executeEditSwimlaneCommand(this); }, unexecute: function(globalService) { globalService.unexecuteEditSwimlaneCommand(this); }, serialize: function() { return JSON.stringify({ objectType: "EditSwimlaneCommand", data:{ oldSwimlaneName: this.oldSwimlaneName, newSwimlaneName: this.newSwimlaneName, oldFillColor: this.oldFillColor.toCSS(true), newFillColor: this.newFillColor.toCSS(true), oldOpacity: this.oldOpacity, newOpacity: this.newOpacity } }); }, deserialize: function(jsonData) { var data = JSON.parse(jsonData); var cmd = new EditSwimlaneCommand(data.data); return cmd; }
  • 6. Document prepared by Steve Widom of Exparency, LLC. }; Notice that this Javascript object carries two sets of data – the new state as well as the old state. It would not be possible to undo this command without knowledge of the old state. Also notice that this object has the responsibility for serializing itself so that it can be placed on the respective queues as well as localStorage as a string, as well as restoring itself from such a string representation. Finally, notice that the an AngularJS service is engaged to perform the actual work on behalf of the application to execute the command and to undo the command (via a call to the object’s unexecute method.) This service is generally global to the entire application and thus is named as such (globalService) and it has various methods for interacting with the various application controllers as well as executing the REST API services. Being over 600 lines of Javascript AngularJS code, it is beyond the scope of this document to show all that detail. Suffice it to say that methods related to pulling drawing data from the backend MySQL database (fill colors, border widths and colors, coordinate data, widths, heights, etc.) along with the other expected CRUD operations are the responsibility of this service. Since this document is focused on the implementation of the Memento Pattern it is not necessary to show all that detail. We can now summarize our implementation pattern for achieving our goal of supporting CTRL-Z and CTRL-Y: 1. For each command in the application create a command object such as the one shown above. 2. In the application controller add code that populates this command object with the new state as well as the original state. The following code snippet is an example that relates to the swimlane example just mentioned. Note that it uses Google’s Material UI Dialog to capture the new swimlane data (name, fill color and opacity). function EditSwimlaneDialogController($scope, $mdDialog, data) { var self = this; self.fillColor = data.swimlane.children[0].fillColor.toCSS(true); self.opacity = data.swimlane.children[0].opacity; self.name = data.swimlane.swimlaneName; $scope.hide = function() { $mdDialog.hide(); }; $scope.cancel = function() { $mdDialog.cancel(); }; $scope.submitEditSwimlaneForm = function() { var json = { oldSwimlaneName: data.swimlane.swimlaneName, newSwimlaneName: self.name, oldFillColor: data.swimlane.children[0].fillColor.toCSS(true), newFillColor: self.fillColor, oldOpacity: data.swimlane.children[0].opacity, newOpacity: self.opacity }; data.globalService.executeEditSwimlaneCommand(new EditSwimlaneCommand(json)); $mdDialog.hide(); }; }
  • 7. Document prepared by Steve Widom of Exparency, LLC. 3. Create a method in the globalService service that executes and unexecutes this command object (possibly making asynchronous REST calls.) Going back to the same example of the swimlane, here is a code snippet for the implementation of the globalService.executeEditSwimlaneCommand() method: executeEditSwimlaneCommand: function(cmd) { this.pushCommand(cmd); var swimlane = this.getDrawing().getSwimlane(this, cmd.oldSwimlaneName); swimlane.fillColor = cmd.newFillColor; swimlane.opacity = cmd.newOpacity; this.getDrawing().changeSwimlaneName(this, swimlane, cmd.newSwimlaneName); this.getDrawing().refreshView(this); }, Finally, the pushCommand method does the work of interacting with the Command Manager: pushCommand: function(cmd) { CommandManager.executed.push(cmd); if (this.getMacroRecordState() == 'Record') { CommandManager.saveCommandToLocalStorageCommandQueue(cmd); } }, Notice that this last code snippet has a logical test to determine whether the application is in Record mode. We only want to store commands in localStorage if we are, indeed, recording these events. A Word About Macros In the Introduction of this document it was mentioned that a positive side effect of the implementation of the Memento Pattern was the ability to record and playback macros that are stored in localStorage. Long term the intent is to store these collections of commands as named macros in the back end database. This should be a significant benefit to the user. Additionally, however, there is a somewhat “selfish” reason for adding this functionality and that is related to customer incidence reporting of a bug or general problem. Oftentimes a CSR or developer, upon initiating a conversation with the customer, will naturally ask what they were doing at the time that the bug or problem occurred. More likely than not the customer will not have those exact details (why would they?) However, if the CSR or developer asks that the customer send them the command queue stored in localStorage (effectively an event log with all the necessary detail) then the developer can likely reproduce the problem in a development environment with a debugger and all. This will hopefully turn out to be a very useful tool in diagnosing customer complaints. Only time will tell.