In the previous blog posts we handled the basics, demonstrated how to work with collections, generic lists and dictionaries related to object persistence in JSON. Below is an overview of part 1, 2 & 3 of this blog series.
This blog post focuses on a feature of TMS FNC Core that manages object history, undo & redo capabilities in one convenient utility class: TTMSFNCUndoManager.
Getting started
To get started using this class, add the unit (FMX.)(VCL.)(WEBLib.)(LCL)TMSFNCUndo.pas to the uses list of your project. This enables you to make use of the TTMSFNCUndoManager class. The TTMSFNCUndoManager has a couple of public methods that can be used to navigate through the object history.
function NextUndoAction: string; //returns the next undo action function NextRedoAction: string; //returns the next redo action function CanUndo: Boolean; //returns a boolean if an undo action is possible function CanRedo: Boolean; //returns a boolean if a redo action is possible procedure Undo; //executes an undo action procedure Redo; //executes a redo action procedure ClearUndoStack; //clears the object history procedure PushState(const {%H-}AActionName: string); //puts an object state on the history stack property MaxStackCount: Integer read FMaxStackCount write FMaxStackCount default 20; //maximum number of history items on the stack
Each instance of the TTMSFNCUndoManager has a unique reference to the object it will manage, meaning that there can only be one manager per object. To instantiate the TTMSFNCUndoManager, call:
MyUndoManager := TTMSFNCUndoManager.Create(MyObject);
Initializing TPerson
Now let's get back to our TPerson implementation we did in the first blog post. We create a TPerson object, and load the JSON data with the known methods.
var p: TPerson; begin p := TPerson.Create; try TTMSFNCObjectPersistence.LoadObjectFromString(p, jsonSample); finally p.Free; end; end;Our TPerson object instance now contains the information from the predefined JSON data sample. We want to keep history of what happens with the TPerson object so we are going to create a TTMSFNCUndoManager instance, managing the object.
var p: TPerson; u: TTMSFNCUndoManager; begin p := TPerson.Create; u := TTMSFNCUndoManager.Create(p); try TTMSFNCObjectPersistence.LoadObjectFromString(p, jsonSample); p.Log; finally u.Free; p.Free; end; end;
The first step is to create an initial version of our object on the history stack, so we can go back to this state whenever is required. To do this, we call
u.PushState('init');Note that "init", can be whatever keyword you want, as long as it can be recognized by the undo manager, and referred to when required. The initial state is pushed after loading the JSON sample data. Whenever we change something directly on the object, we push a state onto the history stack. For example, the code below will change the Name property to "Error", and push an item with the keyword "error_data" on the stack.
p.Name := 'ERROR'; u.PushState('error_data');
The complete code snippet now becomes
var p: TPerson; u: TTMSFNCUndoManager; begin p := TPerson.Create; u := TTMSFNCUndoManager.Create(p); try TTMSFNCObjectPersistence.LoadObjectFromString(p, jsonSample); u.PushState('init'); p.Name := 'ERROR'; u.PushState('error_data'); p.Log; finally u.Free; p.Free; end; end;The p.Log; statement will now log the object and as expected, the name is changed to "Error" in the JSON output.
{ "$type": "TPerson", "Address": { "$type": "TPersonAddress", "AddressLocality": "Colorado Springs", "AddressRegion": "CO", "PostalCode": "80840", "StreetAddress": "100 Main Street" }, "BirthDate": "1979-10-12", "Colleagues": [], "Email": "info@example.com", "Gender": "female", "JobTitle": "Research Assistant", "Name": "ERROR", "Nationality": "Albanian", "Relations": [ { "$type": "TPersonRelation", "Description": "Brother", "Name": "John Doe" }, { "$type": "TPersonRelation", "Description": "Mother", "Name": "Mia Reyes" } ], "Telephone": "(123) 456-6789", "URL": "http://www.example.com" }
Going back in history
The TPerson object now contains a name with the value "ERROR". For the purpose of this blog post, this is simulated, but eventually in a real life example this could be a database read error, or an exception during the usage of the application which results in corrupt data. Now that we use the undo/redo manager and have saved an object state before changing the value to "ERROR", we can now revert back or "undo" the action that led into the error. simply call
u.Undo;
which will go from the error state to the initial state
var p: TPerson; u: TTMSFNCUndoManager; begin p := TPerson.Create; u := TTMSFNCUndoManager.Create(p); try TTMSFNCObjectPersistence.LoadObjectFromString(p, jsonSample); u.PushState('init'); p.Name := 'ERROR'; u.PushState('error_data'); u.Undo; p.Log; finally u.Free; p.Free; end; end;
Logging the object will result in JSON mapping on the initial sample data. As error_data is now a state in the history manager, you can always go back to this state and find out the cause for the issue or load the corrupted data in an analyze tool / application afterwards.
Going forward in history
Going forward in the object history stack, back to the state with the value "ERROR" as a name, we simply call
u.Redo;
Feedback
We are at the end of the 4-part blog series around JSON persistence in FNC. Questions, already working on object persistence? Don't hesitate to ask them in the comments section and/or via our support channel (https://support.tmssoftware.com)