Skip to Content

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:

/wp-content/uploads/2014/02/running_401519.png

 

Do – change of value – moves us forward towards a “new future”, as we could only have one.

/wp-content/uploads/2014/02/running_2_401520.png

 

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.

/wp-content/uploads/2014/02/orig_1_401494.png

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.

orig_2.2.PNG

 

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).

/wp-content/uploads/2014/02/ch_1_401500.png

 

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.

ch_2.PNG

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>0ch_3__.PNG

 

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.

 

To report this post you need to login first.

1 Comment

You must be Logged on to comment or reply to a post.

Leave a Reply