Browse files

SCUI.Undoable Mixin

  • Loading branch information...
1 parent 572acbc commit 340af9cbb3086cee605bc3fc4b9250b1e67a781a @bblatnick bblatnick committed with etgryphon Mar 31, 2011
Showing with 134 additions and 0 deletions.
  1. +81 −0 frameworks/foundation/mixins/undoable.js
  2. +53 −0 frameworks/foundation/tests/mixins/undoable.js
View
81 frameworks/foundation/mixins/undoable.js
@@ -0,0 +1,81 @@
+// ==========================================================================
+// SCUI.Undoable
+// ==========================================================================
+
+/**
+ @namespace
+
+ The SCUI.Undoable mixin makes it easy to automatically register undo operations
+ on your view whenever relevant properties change. To use this mixin, include
+ it in your view and then add the names of the properties you want to trigger
+ an undo register when they change..
+
+ h2. Example
+
+ {{{
+ MyApp.MyViewClass = SC.View.extend(SCUI.Undoable, {
+ undoableProperties: 'title height width'.w(),
+ ...
+ });
+ }}}
+*/
+SCUI.Undoable = {
+
+ /**
+ Add an array with the names of any property on the view that should register
+ an undo of the same property on the undo manager for your view.
+
+ @property {Array}
+ */
+ undoableProperties: [],
+
+ /** @private
+ Register undoable property observer...
+ */
+ initMixin: function() {
+ var valueCache, up = this.get('undoableProperties') ;
+ var idx = up.length ;
+ valueCache = this._undoableProperty_didChange_valueCache = {};
+ while (--idx >= 0) {
+ var key = up[idx];
+ this.addObserver(key, this, this.undoablePropertyDidChange);
+ valueCache[key] = this.get(key);
+ }
+ },
+
+ /**
+ This method is invoked whenever an undoable property changes. It will register
+ the undo of the value that the property was set to.
+ */
+ undoablePropertyDidChange: function(target, key) {
+ var newValue = this.get(key);
+ var valueCache = this._undoableProperty_didChange_valueCache ;
+ var oldValue = valueCache[key];
+
+ if (this.undoablePropertyShouldRegisterUndo(key, oldValue, newValue)) {
+ // register undo operation with old value
+ this.get('undoManager').registerUndo(function() {
+ this.set(key, oldValue);
+ }, this);
+ // update the cache
+ valueCache[key] = newValue;
+ }
+ },
+
+ /**
+
+ Called just before registering the undo operation to give you and
+ opportunity to decide if the register should be allowed. Override
+ for more fine-grained control
+
+ The default implementation returns YES if the value changes.
+
+ @param key {String} the property to register undo
+ @param oldValue old value of property
+ @param newValue new value of property
+ @returns {Boolean} YES to alow, NO to prevent it
+ */
+ undoablePropertyShouldRegisterUndo: function(key, oldValue, newValue) {
+ return (newValue !== oldValue);
+ }
+};
View
53 frameworks/foundation/tests/mixins/undoable.js
@@ -0,0 +1,53 @@
+/*globals SCUI*/
+
+(function() {
+
+ var view;
+
+ module('Undoable', {
+ setup: function() {
+
+ view = SC.View.create(SCUI.Undoable, {
+ undoableProperties: ['foo', 'bar'],
+ foo: null,
+ bar: null
+ });
+ }
+ });
+
+ test('basic undo/redo works', function () {
+ SC.RunLoop.begin();
+ view.set('foo', 'monkey');
+ view.set('bar', 'foot');
+ SC.RunLoop.end();
+
+ equals(view.get('foo'), 'monkey', "set foo to monkey");
+ equals(view.get('bar'), 'foot', "set bar to foot");
+
+ var undoManager = view.get('undoManager');
+
+ SC.RunLoop.begin();
+ undoManager.undo();
+ SC.RunLoop.end();
+
+ equals(view.get('bar'), null, "foo went back to null after undo");
+
+ SC.RunLoop.begin();
+ undoManager.undo();
+ SC.RunLoop.end();
+
+ equals(view.get('foo'), null, "bar went back to null after undo");
+
+ SC.RunLoop.begin();
+ undoManager.redo();
+ SC.RunLoop.end();
+
+ equals(view.get('foo'), 'monkey', "foo went back to monkey after redo");
+
+ SC.RunLoop.begin();
+ undoManager.redo();
+ SC.RunLoop.end();
+
+ equals(view.get('bar'), 'foot', "bar went back to foot after redo");
+ });
+})();

0 comments on commit 340af9c

Please sign in to comment.