Blog
All Blog Posts | Next Post | Previous PostDiving deeper: JSON persistence, part 4/4: Undo & Redo Manager
Wednesday, August 10, 2022
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;
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');
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;
{ "$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;
Sample
https://www.tmssoftware.com/download/samples/Sample-TPerson.zip
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)
Pieter Scheldeman
This blog post has received 3 comments.
Pieter Scheldeman
Pieter Scheldeman
All Blog Posts | Next Post | Previous Post
Thanks for an interesting article serie, about object persistence with JSON.
I have a hard time with all the small pieces of code. Which to use, not to use, and how to put them together.
In the original article back in 28 April 2020: FNC Hidden Gems: JSON persistence, there was a sample code that combined alle the relevant pieces.
So please put together a sample code that combines the relevant code snippets, to a working sample.
May be even make it a new demoprogram for the FNC Core pack.
/HelgeLarsen
Larsen Helge