On Github veggiemonk / slide-immutable-js
http://www.macwright.org/2015/05/18/practical-undo.html Original Article by Tom MacWright
The principles are:
var myself = { name: 'Tom' }; var someoneElse = myself; myself.name = 'Thomas'; // both myself & someoneElse are now equal to { name: 'Thomas' } // change arrays in place. var numbers = [3, 2, 1]; var sorted = numbers.sort(); // both numbers & sorted are equal to [1, 2, 3] // because calling .sort() sorts the array in-place
var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 50); map1.get('b'); // 2 map2.get('b'); // 50 // shared structure var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 2); assert(map1 === map2); // no change var map3 = map1.set('b', 50); assert(map1 !== map3); // change
var myself = Immutable.Map({ name: 'Tom' }); // Instead of changing myself in-place // like I would with vanilla JavaScript, // Immutable provides methods that yield new modified objects. var someoneElse = myself.set('name', 'Thomas'); // If you've dealt with this problem before, // you might notice that there's another way // of approaching this problem ==> by cloning objects: var myself = { name: 'Tom' }; // clone myself, to ensure that changing someoneElse doesn't // mutate it. var someoneElse = JSON.parse(JSON.stringify(myself)); myself.name = 'Thomas';
var myself = { name: 'Tom' }; var someoneElse = JSON.parse(JSON.stringify(myself)); myself.name = 'Thomas';
Operations are functions that create new versions
// person is the data, and height is a property // we want to change. this function creates a new // version of person *without modifying the old one* function changeHeight(person, height) { return person.set('height', height); }
As simple as that: generally the start of the array is the first state, where there's no ability to go backwards, and the tip of the array is the present.
var historyIndex = 0; var history = [Immutable.Map({ name: 'Tom' })]; // history doesn't need to be immutable!
push a new version on the stack, and run an operation
function operation(fn) { // eliminate the future history = history.slice(0, historyIndex + 1); // create a new version by applying an operation to the head var newVersion = fn(history[historyIndex]); history.push(newVersion); historyIndex++; }
function changeHeight(height) { operation(function(data) { return data.set('height', height); }); }
var hasUndo = historyIndex !== 0; var hasRedo = historyIndex !== history.length - 1;
Please change me!
Along with an operation, you'll add a little text snippet, like "Drew a circle" or "Changed a color".
function operation(fn, annotation) { // eliminate the future annotations = annotations.slice(0, historyIndex + 1); history = history.slice(0, historyIndex + 1); // create a new version by applying an operation to the head var newVersion = fn(history[historyIndex]); history.push(newVersion); annotations.push(annotation); historyIndex++; } // an operation that adds an annotation function changeHeight(height) { operation(function(data) { return data.set('height', height); }, 'Changed the height'); }