Blog

All Blog Posts  |  Next Post  |  Previous Post

Diving deeper: JSON persistence, part 4/4: Undo & Redo Manager

Bookmarks: 

Wednesday, August 10, 2022

TMS Software Delphi  Components

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.

  1. Basics
  2. Collections
  3. Generics

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)



Pieter Scheldeman


Bookmarks: 

This blog post has received 2 comments.


1. Thursday, August 11, 2022 at 12:31:06 PM

Hello
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


2. Thursday, August 11, 2022 at 5:26:57 PM

Agreed, A sample demonstrating all functionality is being prepared. I''ll update this blog post with a link next week.

Pieter Scheldeman




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post