Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member202465
Contributor
0 Kudos


Editing table data consists of several types of changes, the basic ones are: Insert new row, delete row, and update row values. To allow Undo / Redo mechanism for our changes we need to record the type of change along with the changed values.

 

In the earlier document about Undo / Redo we covered the scenario of editing values in a Form. That was a bit more simple as it allows only an "update" type of change.

 

Motivation:

Give better value to the user, in relevant scenarios. Any scenario of editing data is a candidate!

Recovery: It is possible to reproduce older values while editing

Better Performance: Working faster as errors could be reverted

 

 

 

The Application

Here is a simple example of editing an order of playing instruments, demonstrating:

  • Adding a new instrument

  • Updating an instrument

  • Deleting an instrument

  • 3 x Undo

  • 3 x Redo




 

 

Here is the same example with the debugging panel made visible. Notice in the past and future lists the "action" field. This tells us the type of change, so we know how to handle it in Undo / Redo:



 

As we can guess from seeing the debugging panel, The Undo / Redo mechanism is unaware of features used here like the Summary chart or the table grouping. This is just as well since we want the minimum dependency between business and transactional modeling.

 

 

 

Insert, Delete and Update

Basically, we could think of this as if we are going through the data changes we make: Undo moves us back, Redo moves us forward:



 

Do - change of value - moves us forward towards a "new future", as we could only have one.



 

Here is a very general description of the Undo / Redo operations, based on the type of change:

 









































Type of change Undo Redo Do - User Change
Get the record recently added to the "past" Get the record recently added to the "future" Empty the "future" list
Move record from "past" to "future" Move record from "future" to "past"
Update set the relevant row to have the defined older values set the relevant row to have the defined newer values Add the pre-change row to the "past" with the action "update"
Delete insert a new row with the defined values delete the row with these values Add the deleted row to the "past" with action "delete"
Insert delete the row with these values insert a new row with the defined values Add the new row to the "past" with action "insert"

 

 

 

Modeling

We add the Undo / Redo functionality on top of an existing model. I will describe the business part in short:

1. We use a Grid View for the output of a Data Service.

2. For Insert Row operation we use a Transition link to open a Popup - the Add action is define on a button in the Popup Layout.

3. Optional - Use an Aggregate Operator to summarize the data and present it in a Pie Chart.

4. Optional - grouping on the Grid View is done for the "category" field: List Viewer Configuration => Group By: category.



5. In the Layout we see the Add button in the Grid View's toolbar, it contains the Custom Action "add".

6. Two Buttons were added as columns for the Delete and Save actions.



 

Model the Undo / Redo functionality:

7. Add Data Share elements for the past, present and future.

8. Connect the output of the Data Service to the present.

9. In each one hold the same set of fields as that of the Table.

10. Add the "action" field to describe the type of change (insert / delete / update).



 

11. Add a Panel element, set its visibility to false (in the Configuration Panel).


12. Connect Grid View elements to the Data Shares, and place them inside the Panel. They are used for debugging / testing.

13. Add a Data Share element and name it size.

14. Define the following numeric fields: past and future. This is later used to set or get the size of our lists. Knowing the size we could disable / enable the Undo and Redo buttons as appropriate.


15. Add a Data Share element and rename it "current". In the Define Data dialog set its cardinality to "Record", and the fields to be the same as in the "past" Data Share.



We need the "present" part for the simple reason of changed values in a table row: When we have the chance to handle the change we only know the new values. This way we could get the former values from the "present", using the FIND action.

 

The "current" Data Share: Our Undo / Redo mechanism removes the recent record from the past / future. It also needs to perform the correct set of Actions according to this record's type (insert / delete / update). So we copy it beforehand to this temporary memory. We then use it in the Actions condition to get the specific set of actions, and reset it when it's done. See here.

 

16. In the Layout Board add two buttons for Undo and Redo to the toolbar.

17. Define the Enabled condition for the Undo Button: =size@past>0

18. Define the Enabled condition for the Redo Button: =size@future>0

 

19. Define the Action for the OK button on the popup - inserting a new row:























































Action



Parameters



Condition


Remarks

COPYTO



From: Form1


To: Present


Scope: Current row


Insert: After


Add record to "present"

COPYTO



From: Form1


To: past


Scope: Current row


Insert: First


Map Data: Give action the value insert


Add record to the "past"

COPYTO



From:Form1


To: Products


Scope: Current row


Insert: After


Add record to the Table

DELETE



From: Future


Delete: All rows



=size@future>0


Empty "future"

ASSIGN



From: [none]


Value: =size@past+1


To: size


Target Field: past



ASSIGN



From: [none]


Value: 0


To: size


Target Field: future



CLOSE



Window: Popup1



 

 

20. Define the Action for the Delete button - delete a row:

















































Action



Parameters



Condition


Remarks

FIND



Find in: present


Search for: =@code==Products@code


Locate the row in "present"

MOVETO



From: present


To: past


Scope: Current row


Insert: First


Map Data: Give action the value delete


Add record to the "past"

DELETE



From: Products


Delete: Current row


Delete the row from the table

DELETE



From: Future


Delete: All rows



=size@future>0


Empty "future"

ASSIGN



From: [none]


Value: =size@past+1


To: size


Target Field: past



ASSIGN



From: [none]


Value: 0


To: size


Target Field: future



 

 

21. Define the Action for the Save button - update the row:
















































Action



Parameters



Condition


Remarks

FIND



Find in: present


Search for: =@code==Products@code


Locate the row in "present"

MOVETO



From: present


To: past


Scope: Current row


Insert: First


Map Data: Give action the value update


Add record with pre-change values to the "past"

COPYTO



From: Products


To: present


Scope: Current row


Insert: After


Update relevant record in the "present"

DELETE



From: Future


Delete: All rows



=size@future>0


Empty "future"

ASSIGN



From: [none]


Value: =size@past+1


To: size


Target Field: past



ASSIGN



From: [none]


Value: 0


To: size


Target Field: future



 

 

 

For the Undo / Redo we need to perform a subset of the action list, according to the type of the transaction. We use the Condition to define this.


22. Define Action for the Undo button.


















































































































Action



Parameters



Condition



Remarks



COPYTO



From: past


To: Current


Scope: Current row


Insert: After


Copy record to temporary memory

FIND



Find in: present


Search for: =@code==past@code



=current@action=="update"


Locate the row in "present"

FIND



Find in: Products


Search for: =@code==past@code


Locate the row in the Table

DELETE



From: present


Delete: Current row



COPTYTO



From: past


To: present


Scope: Current row


Insert: After



MOVETO



From: Products


To: future


Scope: Current row


Insert: First


Map Data: Give action the value update



MOVETO



From: past


To: Products


Scope: Current row


Insert: After



ASSIGN



From: [none]


Value:


To: current


Target field: action



End of "update" undo. reset temporary memory



COPYTO



From: past


To: Products


Scope: Current row


Insert: After



=current@action=="delete"



COPYTO



From: past


To: future


Scope: Current row


Insert: First



MOVETO



From: past


To: present


Scope: Current row


Insert: After



ASSIGN



From: [none]


Value:


To: current


Target field: action



End of "delete" undo. reset temporary memory



FIND



Find in: present


Search for: =@code==past@code



=current@action=="insert"


Locate the row in "present"

DELETE



From: present


Delete: Current row



FIND



Find in: Products


Search for: =@code==past@code


Locate the row in the Table

DELETE



From: Products


Delete: Current row



MOVETO



From: past


To: future


Scope: Current row


Insert: First



ASSIGN



From: [none]


Value: =size@future+1


To: size


Target Field: future



ASSIGN



From: [none]


Value: =size@past-1


To: size


Target Field: past



 

 

23. Define Action for the Redo button. This has the same set of actions as Undo, with a few changes in Parameters:


















































































































Action



Parameters



Condition



Remarks



COPYTO



From: future
To: Current
Scope: Current row
Insert: After



FIND



Find in: present
Search for: =@code==past@code



=current@action=="update"


Locate the row in "present"

FIND



Find in: Products
Search for: =@code==past@code


Locate the row in the Table

DELETE



From: present
Delete: Current row



COPTYTO



From: future
To: present
Scope: Current row
Insert: After



MOVETO



From: Products
To: past
Scope: Current row
Insert: First
Map Data: Give action the value update



MOVETO



From: future
To: Products
Scope: Current row
Insert: After



ASSIGN



From: [none]
Value:
To: current
Target field: action



End of "update" redo. reset temporary memory



COPYTO



From: future
To: Products
Scope: Current row
Insert: After



=current@action=="insert"



COPYTO



From: future
To: past
Scope: Current row
Insert: First



MOVETO



From: future
To: present
Scope: Current row
Insert: After



ASSIGN



From: [none]
Value:
To: current
Target field: action



End of "insert" redo. reset temporary memory



FIND



Find in: present
Search for: =@code==future@code



=current@action=="delete"


Locate the row in "present"

DELETE



From: present
Delete: Current row



FIND



Find in: Products
Search for: =@code==future@code


Locate the row in the Table

DELETE



From: Products
Delete: Current row



MOVETO



From: future
To: past
Scope: Current row
Insert: First


 

ASSIGN



From: [none]
Value: =size@future-1
To: size
Target Field: future



ASSIGN



From: [none]
Value: =size@past+1
To: size
Target Field: past



 

 

That's it.

 

Some points to consider:

  • Separation of business and transactional modeling: The more we separate these two, the easier to maintain / change the model.

  • Performance: While it's possible to copy all the data for each change, we will then end up with several transactions X number of rows. So we hold in the past / future lists only the change.

  • Transactions: It's possible to envelope several changes under one recorded action. This is what we do for the Save action. In other scenarios it might be required to relate to several changes as one atomic transaction.

  • Finding rows using the FIND Action: We rely on having a unique identifier to locate the relevant row (in our data table or "present" list). In the absence of one, we could use the combination of several fields. If there is no guarantee there as well, we might want to consider having row numbers, like in Paging records in the UI - Coding our own Web Service to get Row Numbers.

  • Making changes to the model: When changing the "business part", make sure to adjust the following:

    • The set of fields in the past, present, future and current elements.

    • In COPYTO / MOVETO actions, make sure the Map Data has the correct field assignments.




 

1 Comment