+ });
+ },
+
+ ifChanged: function(context, action, documentChangedHandler, documentUnchangedHandler) {
+ var hasChanged = false,
+ changeMonitor = function() {
+ hasChanged = true;
+ };
+
+ this.on('change', changeMonitor);
+ action.call(context);
+ this.off('change', changeMonitor);
+
+ if(hasChanged) {
+ if(documentChangedHandler) {
+ documentChangedHandler.call(context);
+ }
+ } else {
+ if(documentUnchangedHandler) {
+ documentUnchangedHandler.call(context);
+ }
+ }
+ },
+
+ transform: function(Transformation, args) {
+ var toret, transformation;
+
+ if(typeof Transformation === 'function') {
+ transformation = new Transformation(this, this, args);
+ } else {
+ transformation = Transformation;
+ }
+ if(transformation) {
+ this._transformationLevel++;
+
+ this.ifChanged(
+ this,
+ function() {
+ toret = transformation.run({beUndoable:this._transformationLevel === 1});
+ },
+ function() {
+ if(this._transformationLevel === 1 && !this._undoInProgress) {
+ if(this._transactionInProgress) {
+ this._transactionStack.push(transformation);
+ } else {
+ this.undoStack.push(transformation);
+ }
+ }
+ if(!this._undoInProgress && this._transformationLevel === 1) {
+ this.redoStack = [];
+ }
+ }
+ );
+
+ this._transformationLevel--;
+ return toret;
+ } else {
+ throw new Error('Transformation ' + transformation + ' doesn\'t exist!');
+ }
+ },
+ undo: function() {
+ var transformationObject = this.undoStack.pop(),
+ doc = this,
+ transformations, stopAt;
+
+ if(transformationObject) {
+ this._undoInProgress = true;
+
+ if(_.isArray(transformationObject)) {
+ // We will modify this array in a minute so make sure we work on a copy.
+ transformations = transformationObject.slice(0);
+ } else {
+ // Lets normalize single transformation to a transaction containing one transformation.
+ transformations = [transformationObject];