}
});
- if(idx !== 'undefined') {
+ if(idx !== undefined) {
nodePath = nodePath.slice(0, idx);
}
toret = nodePath.map(function(node) {return node.getIndex(); });
var node = (metaData && metaData.node) ? metaData.node : this,
event = new events.ChangeEvent(type, $.extend({node: node}, metaData || {}));
if(type === 'nodeDetached' || this.document.containsNode(event.meta.node)) {
+ if(type === 'nodeMoved') {
+ event.meta.parent = origParent;
+ }
this.document.trigger('change', event);
}
if((type === 'nodeAdded' || type === 'nodeMoved') && !this.document.containsNode(this) && nodeWasContained) {
return toret;
},
+ containsNode: function(node) {
+ return node && (node.nativeNode === this.nativeNode || node._$.parents().index(this._$) !== -1);
+ },
+
toXML: function() {
var wrapper = $('<div>');
wrapper.append(this._getXMLDOMToDump());
var Document = function(xml, extensions) {
this.undoStack = [];
this.redoStack = [];
- this._transactionStack = [];
+ this._currentTransaction = null;
this._transformationLevel = 0;
this._nodeMethods = {};
},
containsNode: function(node) {
- return this.root && (node.nativeNode === this.root.nativeNode || node._$.parents().index(this.root._$) !== -1);
+ return this.root && this.root.containsNode(node);
},
getSiblingParents: function(params) {
return noSiblingParents;
}
- var i;
- for(i = 0; i < Math.min(parents1.length, parents2.length); i++) {
+ var stop = Math.min(parents1.length, parents2.length),
+ i;
+ for(i = 0; i < stop; i++) {
if(parents1[i].sameNode(parents2[i])) {
continue;
}
break;
}
+ if(i === stop) {
+ i--;
+ }
return {node1: parents1[i], node2: parents2[i]};
},
});
},
+ 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(!this._currentTransaction) {
+ return this.transaction(function() {
+ return this.transform(Transformation, args);
+ }, this);
+ }
+
if(typeof Transformation === 'function') {
transformation = new Transformation(this, this, args);
} else {
}
if(transformation) {
this._transformationLevel++;
- toret = transformation.run({beUndoable:this._transformationLevel === 1});
- if(this._transformationLevel === 1 && !this._undoInProgress) {
- if(this._transactionInProgress) {
- this._transactionStack.push(transformation);
- } else {
- this.undoStack.push(transformation);
+
+ this.ifChanged(
+ this,
+ function() {
+ toret = transformation.run({beUndoable:this._transformationLevel === 1});
+ },
+ function() {
+ if(this._transformationLevel === 1 && !this._undoInProgress) {
+ this._currentTransaction.pushTransformation(transformation);
+ }
+ if(!this._undoInProgress && this._transformationLevel === 1) {
+ this.redoStack = [];
+ }
}
- }
- if(!this._undoInProgress && this._transformationLevel === 1) {
- this.redoStack = [];
- }
+ );
+
this._transformationLevel--;
return toret;
} else {
}
},
undo: function() {
- var transformationObject = this.undoStack.pop(),
+ var transaction = this.undoStack.pop(),
doc = this,
transformations, stopAt;
- if(transformationObject) {
+ if(transaction) {
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];
- }
+ // We will modify this array in a minute so make sure we work on a copy.
+ transformations = transaction.transformations.slice(0);
if(transformations.length > 1) {
// In case of real transactions we don't want to run undo on all of transformations if we don't have to.
});
this._undoInProgress = false;
- this.redoStack.push(transformationObject);
+ this.redoStack.push(transaction);
}
},
redo: function() {
- var transformationObject = this.redoStack.pop(),
- transformations;
- if(transformationObject) {
+ var transaction = this.redoStack.pop();
+ if(transaction) {
this._transformationLevel++;
- transformations = _.isArray(transformationObject) ? transformationObject : [transformationObject];
- transformations.forEach(function(t) {
+ transaction.transformations.forEach(function(t) {
t.run({beUndoable: true});
});
this._transformationLevel--;
- this.undoStack.push(transformationObject);
+ this.undoStack.push(transaction);
}
},
- startTransaction: function() {
- if(this._transactionInProgress) {
+ startTransaction: function(metadata) {
+ if(this._currentTransaction) {
throw new Error('Nested transactions not supported!');
}
- this._transactionInProgress = true;
+ this._currentTransaction = new Transaction([], metadata);
},
endTransaction: function() {
- if(!this._transactionInProgress) {
+ if(!this._currentTransaction) {
throw new Error('End of transaction requested, but there is no transaction in progress!');
}
- this._transactionInProgress = false;
- if(this._transactionStack.length) {
- this.undoStack.push(this._transactionStack);
- this._transactionStack = [];
+ if(this._currentTransaction.hasTransformations()) {
+ this.undoStack.push(this._currentTransaction);
}
+ this._currentTransaction = null;
+ },
+
+ transaction: function(callback, context, metadata) {
+ var toret;
+ this.startTransaction(metadata);
+ toret = callback.call(context);
+ this.endTransaction();
+ return toret;
},
getNodeByPath: function(path) {
_defineDocumentProperties: function($document) {
var doc = this;
Object.defineProperty(doc, 'root', {get: function() {
+ if(!$document) {
+ return null;
+ }
return doc.createDocumentNode($document[0]);
}, configurable: true});
Object.defineProperty(doc, 'dom', {get: function() {
+ if(!$document) {
+ return null;
+ }
return $document[0];
}, configurable: true});
}
});
+var Transaction = function(transformations, metadata) {
+ this.transformations = transformations || [];
+ this.metadata = metadata;
+};
+$.extend(Transaction.prototype, {
+ pushTransformation: function(transformation) {
+ this.transformations.push(transformation);
+ },
+ hasTransformations: function() {
+ return this.transformations.length > 0;
+ }
+});
+
return {
documentFromXML: function(xml) {