senchacon 2016: handling undo-redo in sencha applications - nickolay platonov

45
Handling Undo-Redo in Sencha Applications Nickolay Platonov @Bryntum

Upload: sencha

Post on 12-Jan-2017

72 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Handling Undo-Redo in Sencha Applications

Nickolay Platonov@Bryntum

Page 2: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

About me

• Nickolay Platonov- Senior ExtJS developer at Bryntum

- Using ExtJS since version 2.0

- SamuraiJack1 on the forums

- Creator of Joose 3 class system for JavaScript, http://joose.it

- Co-creator of Siesta and Robo tools

- [email protected]

2

Page 3: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo in general

Page 4: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo as a feature

• Very useful

• Standard feature for any serious desktop application

4

Page 5: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo as a feature

• Very useful

• Standard feature for any serious desktop application

• Rarely seen in Sencha applications

• Probably since it is considered too complex to implement

5

Page 6: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo as a feature

• Very useful

• Standard feature for any serious desktop application

• Rarely seen in Sencha applications

• Probably since it is considered too complex to implement

• But doable, as you will see by the end of this talk

6

Page 7: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo – formal task definition

• It’s all about the application state

• State is presented with data structures

// Data structure for shapevar shape = { shapeType : ‘circle’,

x : 100,y : 100,radius : 50

}

Page 8: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo – formal task definition

• It’s all about the application state

• State is presented with data structures

• For example, for simple graphical editor a state would be – an array of shapes.

// Data structure for shapevar shape = { shapeType : ‘circle’,

x : 100,y : 100,radius : 50

}

// Graphical editor statevar appState = [ shape1, shape2, shape3 ]

Page 9: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo – formal task definition

• Every user action switches (advances) the application to a new state

• User moves a circle

• User adds a shape

{ { shapeType : ‘circle’, shapeType : ‘circle’, x : 100, x : 200, y : 100, y : 200, radius : 50 radius : 50} }

[ shape1, shape 2 ] [ shape1, shape2, shape3 ]

Page 10: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo – formal task definition

• State change is normally a one-way flow

10

state1 state2 state3

Page 11: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Undo/Redo – formal task definition

• We want to make the flow bidirectional

11

state1 state2 state3

Page 12: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Approach 1 (naive) – Save full snapshot

• Serialize full snapshot of application state

• Deserialize the snapshot and place it as a new application state

12

state1 state2 state3

Page 13: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Approach 1 (naive) – Save full snapshot

• Serialize full snapshot of application state

• Deserialize the snapshot and place it as a new application state

• Pros- Very simple implementation

• Cons- Performance (full scan of the dataset)

- Memory consumption (every checkpoint contains all the data)

13

state1 state2 state3

Page 14: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Approach 2 – Save the diff between the states

• Calculate the diff between the application states

• Apply the diff to initial state, to get the new state

14

state1 state2 state3diff_1 diff_2

Page 15: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Approach 2 – Save the diff between the states

• Calculate the diff between the application states

• Apply the diff to initial state, to get the new state

• Pros- Memory consumption (only the actually changed data is gathered)

• Cons- Complexity of diff operation

- Performance of diff operation (both for gathering diffs and applying diffs)

15

state1 state2 state3diff_1 diff_2

Page 16: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Approach 3 – Save the changelog between the states

• Every diff between the states is a list of actions

• Actions are small, atomic and reversible

16

state1 state2 state3Action1 Action2 Action3 Action4

Page 17: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Approach 3 – Save the changelog between the states

• Every diff between the states is a list of actions

• Actions are small, atomic and reversible

• Pros- Performance & memory consumption (only the actually changed data is gathered/stored)

- Relatively simple implementation

• Cons- Application needs to be aware about the undo/redo feature

17

state1 state2 state3Action1 Action2 Action3 Action4

Page 18: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Architecture requirements

• App should follow the MVC pattern

• No state to be kept in the views or controllers (or at least as less as possible)

18

Controller

ModelView

Page 19: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Robo

Page 20: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Robo

• Undo/redo functionality framework, developed by Bryntum

• Targets Sencha applications

• Robo supports ExtJS 5.1.2 / 6.0.1 / 6.0.2 / 6.2.0

• Out of the box, operates on ExtJS data stores (Ext.data.Store and Ext.data.TreeStore)

• Can be customized to a specific application needs

20

Page 21: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Design & terminology

• The transition between application states is called “transaction”

• Every transaction may contain several smaller “actions”, which are all atomic

• Listens to events from stores, and creates actions from them

21

Robo.Transaction

state1 state2Robo.Action 1 Robo.Action 2

Page 22: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

22

http://www.bryntum.com/examples/robo-latest/examples/basic/

Page 23: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Transaction boundaries

• The application may define complex processing rules for data

• Robo is not aware of them

• Developer can choose between the 2 strategies for defining the state checkpoints:- Timeout (default) – finish the transaction after some time

- Manual – finish every transaction manually (it will start automatically on any change in any store)

23

Page 24: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Action example

• Robo.action.flat.Add

• Every action has “undo” and “redo” methods

Ext.define('Robo.action.flat.Add', { extend : 'Robo.action.Base',

store : null, records : null,

index : null,

undo : function () {

this.store.remove(this.records); },

redo : function () { this.store.insert(this.index, this.records); } });

Page 25: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Action example

• Robo.action.flat.Remove

• Every action has “undo” and “redo” methods

Ext.define('Robo.action.flat.Remove', { extend : 'Robo.action.Base',

store : null, records : null,

index : null,

undo : function () { var me = this;

me.store.insert(me.index, me.records); },

redo : function () { var me = this;

me.store.remove(me.records); } });

Page 26: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Integration with Sencha application

• Add Robo.data.Model mixing to the models

Ext.define('Example.model.Employee', { extend : 'Ext.data.Model', modelName : 'Employee', mixins : { robo : 'Robo.data.Model' }, ...})

Page 27: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Integration with Sencha application

• Add Robo.data.Model mixing to the models

• Create an instance of Robo.Manager

Ext.define('Example.model.Employee', { extend : 'Ext.data.Model', modelName : 'Employee', mixins : { robo : 'Robo.data.Model' }, ...})

var robo = new Robo.Manager({ stores : [ 'store1', 'store2', 'store3‘ ]});

Page 28: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Integration with Sencha application

• Add Robo.data.Model mixing to the models

• Create an instance of Robo.Manager

• Add stores to it

Ext.define('Example.model.Employee', { extend : 'Ext.data.Model', modelName : 'Employee', mixins : { robo : 'Robo.data.Model' }, ...})

var robo = new Robo.Manager({ stores : [ 'store1', 'store2', 'store3‘ ]});

// or add the stores after instantiationrobo.addStore(store1)robo.addStore(store2)

Page 29: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Integration with Sencha application

• Add Robo.data.Model mixing to the models

• Create an instance of Robo.Manager

• Add stores to it

• Start monitoring data changes

Ext.define('Example.model.Employee', { extend : 'Ext.data.Model', modelName : 'Employee', mixins : { robo : 'Robo.data.Model' }, ...})

var robo = new Robo.Manager({ stores : [ 'store1', 'store2', 'store3‘ ]});

// or add the stores after instantiationrobo.addStore(store1)robo.addStore(store2)

// start monitoring (after data load)robo.start();

Page 30: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Integration with Sencha application

• Add Robo.data.Model mixing to the models

• Create an instance of Robo.Manager

• Add stores to it

• Start monitoring data changes

• Use the robo.undo() robo.redo() API calls

Ext.define('Example.model.Employee', { extend : 'Ext.data.Model', modelName : 'Employee', mixins : { robo : 'Robo.data.Model' }, ...})

var robo = new Robo.Manager({ stores : [ 'store1', 'store2', 'store3‘ ]});

// or add the stores after instantiationrobo.addStore(store1)robo.addStore(store2)

// start monitoring (after data load)robo.start();

// at some point laterrobo.undo()

robo.redo()

Page 31: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Dependent data

• Data objects often depends on each other

• Change in one object triggers a change in another (possibly in another store)

// user actionUSER: employee1.set(‘hourlyRate’, 100)

// application triggers (activated by ‘update’ event)APP: employee1.set(‘monthlySalary’, 16000)APP: employee1.set(‘yearlySalary’, 192000)

Page 32: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Dependent data

• Data objects often depends on each other

• Change in one object triggers a change in another (possibly in another store)

• Robo performs the undo/redo using the standard data package API (will trigger standard events)

• App may react on every data change triggered by Robo

// user actionUSER: employee1.set(‘hourlyRate’, 100)

// application triggers (activated by ‘update’ event)APP: employee1.set(‘monthlySalary’, 16000)APP: employee1.set(‘yearlySalary’, 192000)

// robo.undo()

ROBO: employee1.set(‘yearlySalary’, 96000)APP: employee1.set(‘hourlyRate’, 50)APP: employee1.set(‘monthlySalary’, 8000)ROBO: employee1.set(‘monthlySalary’, 8000)APP: employee1.set(‘hourlyRate’, 50)APP: employee1.set(‘yearlySalary’, 96000)ROBO: employee1.set(‘hourlyRate’, 50)APP: employee1.set(‘monthlySalary’, 8000)APP: employee1.set(‘yearlySalary’, 96000)

Page 33: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Solution

• Application needs to be aware about current data flow “mode” – “normal/undo/redo”

• Skip the data propagation rules in “undo/redo” mode

33

Page 34: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Technically

• Add the Robo.data.Store mixin to the store

• Use the isUndoingOrRedoing() method to check if current flow is undo/redo

Ext.define('Example.store.EmployeeList', { extend : 'Ext.data.Store', mixins : { robo : 'Robo.data.Store' }, onRecordUpdate : function (...) { if (!this.isUndoingOrRedoing()) { ... } }});

Page 35: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Suspended events

• Robo can’t record anything, if events on a store are suspended

• Moreover, a missed action leads to inconsistent undo/redo queue state

• Application should not change store data if events are suspended (or, suspend events with the queuing option)

• Or, create missing actions manually roboManager.currentTransaction.addAction()

35

Page 36: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Robo widgets. Transaction titles

• Robo provides two simple buttons – undo and redo

• Every button contains a menu with an item for every transaction to undo/redo

• Developer can define a title for the transaction by implementing the “getTitle()” method on the models

36

Page 37: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Advanced Robo showcase

Page 38: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Bryntum Ext Gantt

• 5 stores, one of them is a TreeStore

• Very complex processing rules (change in one store propagates to others)

me.undoManager = new Gnt.data.undoredo.Manager({ stores : [ calendarManager, taskStore, resourceStore, assignmentStore, dependencyStore ]});

Page 39: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Bryntum Ext Gantt

• 5 stores, one of them is a TreeStore

• Very complex processing rules (change in one store propagates to others)

• Works like a charm

me.undoManager = new Gnt.data.undoredo.Manager({ stores : [ calendarManager, taskStore, resourceStore, assignmentStore, dependencyStore ]});

Page 40: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Bryntum Ext Gantt

• 5 stores, one of them is a TreeStore

• Very complex processing rules (change in one store propagates to others)

• Works like a charm

• Required some customization

me.undoManager = new Gnt.data.undoredo.Manager({ stores : [ calendarManager, taskStore, resourceStore, assignmentStore, dependencyStore ]});

Page 41: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

41

http://www.bryntum.com/examples/gantt-latest/examples/advanced/advanced.html#en

Page 42: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Conclusion

• With Robo, the undo-redo functionality is easy to add to any Sencha application, following a few simple rules during development

• There are already several successful implementations

42

Page 43: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Conclusion

• With Robo, the undo-redo functionality is easy to add to any Sencha application, following a few simple rules during development

• There are already several successful implementations

• Next time you hear the request for undo/redo – don’t reject it immediately.

43

Page 44: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov

Learn more

• http://www.bryntum.com/products/robo/

• http://www.bryntum.com/docs/robo/#!/guide/robo_getting_started

• Any questions?

44

Page 45: SenchaCon 2016: Handling Undo-Redo in Sencha Applications - Nickolay Platonov