X-Git-Url: https://git.mdrn.pl/fnpeditor.git/blobdiff_plain/d3bd4d6524e46d6732f758b2854afeff1c890b2a..4b866f317d1e2e774e55127ba6bf598d61b363d8:/src/smartxml/smartxml.js?ds=inline
diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js
index 6f12667..102a506 100644
--- a/src/smartxml/smartxml.js
+++ b/src/smartxml/smartxml.js
@@ -10,9 +10,6 @@ define([
'use strict';
/* globals Node */
-var TEXT_NODE = Node.TEXT_NODE;
-
-
var DocumentNode = function(nativeNode, document) {
if(!document) {
@@ -36,14 +33,20 @@ $.extend(DocumentNode.prototype, {
},
clone: function() {
- var clone = this._$.clone(true, true);
- // clone.find('*').addBack().each(function() {
- // var n = $(this);
- // if(n.data('canvasElement')) {
- // n.data('canvasElement', $.extend(true, {}, n.data('canvasElement')));
- // n.data('canvasElement').$element = n.data('canvasElement').$element.clone(true, true);
- // }
- // });
+ var clone = this._$.clone(true, true),
+ node = this;
+ clone.find('*').addBack().each(function() {
+ var el = this,
+ clonedData = $(this).data();
+
+ _.pairs(clonedData).forEach(function(pair) {
+ var key = pair[0],
+ value = pair[1];
+ if(_.isFunction(value.clone)) {
+ clonedData[key] = value.clone(node.document.createDocumentNode(el));
+ }
+ });
+ });
return this.document.createDocumentNode(clone[0]);
},
@@ -51,7 +54,7 @@ $.extend(DocumentNode.prototype, {
if(!(this.document.containsNode(this))) {
return null;
}
-
+
var nodePath = [this].concat(this.parents()),
toret, idx;
ancestor = ancestor || this.document.root;
@@ -63,7 +66,7 @@ $.extend(DocumentNode.prototype, {
}
});
- if(idx !== 'undefined') {
+ if(idx !== undefined) {
nodePath = nodePath.slice(0, idx);
}
toret = nodePath.map(function(node) {return node.getIndex(); });
@@ -120,6 +123,9 @@ $.extend(DocumentNode.prototype, {
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) {
@@ -200,6 +206,10 @@ $.extend(ElementNode.prototype, {
return toret;
},
+ containsNode: function(node) {
+ return node && (node.nativeNode === this.nativeNode || node._$.parents().index(this._$) !== -1);
+ },
+
toXML: function() {
var wrapper = $('
');
wrapper.append(this._getXMLDOMToDump());
@@ -232,18 +242,21 @@ $.extend(TextNode.prototype, {
var parseXML = function(xml) {
- return $($.trim(xml))[0];
+ var toret = $($.trim(xml));
+ if(!toret.length) {
+ throw new Error('Unable to parse XML: ' + xml);
+ }
+ return toret[0];
+
};
var registerTransformation = function(desc, name, target) {
var Transformation = transformations.createContextTransformation(desc, name);
- //+ to sie powinna nazywac registerTransformationFromDesc or sth
- //+ ew. spr czy nie override (tylko jesli powyzej sa prototypy to trudno do nich dojsc)
target[name] = function() {
var instance = this,
args = Array.prototype.slice.call(arguments, 0);
return instance.transform(Transformation, args);
- }
+ };
};
var registerMethod = function(methodName, method, target) {
@@ -257,10 +270,10 @@ var registerMethod = function(methodName, method, target) {
};
-var Document = function(xml) {
- this.loadXML(xml);
+var Document = function(xml, extensions) {
this.undoStack = [];
this.redoStack = [];
+ this._transactionStack = [];
this._transformationLevel = 0;
this._nodeMethods = {};
@@ -269,6 +282,13 @@ var Document = function(xml) {
this._nodeTransformations = {};
this._textNodeTransformations = {};
this._elementNodeTransformations = {};
+
+ this.registerExtension(coreTransformations);
+
+ (extensions || []).forEach(function(extension) {
+ this.registerExtension(extension);
+ }.bind(this));
+ this.loadXML(xml);
};
$.extend(Document.prototype, Backbone.Events, {
@@ -277,17 +297,25 @@ $.extend(Document.prototype, Backbone.Events, {
createDocumentNode: function(from) {
if(!(from instanceof Node)) {
- if(from.text !== undefined) {
- /* globals document */
- from = document.createTextNode(from.text);
+ if(typeof from === 'string') {
+ from = parseXML(from);
+ this.normalizeXML(from);
} else {
- var node = $('<' + from.tagName + '>');
-
- _.keys(from.attrs || {}).forEach(function(key) {
- node.attr(key, from.attrs[key]);
- });
+ if(from.text !== undefined) {
+ /* globals document */
+ from = document.createTextNode(from.text);
+ } else {
+ if(!from.tagName) {
+ throw new Error('tagName missing');
+ }
+ var node = $('<' + from.tagName + '>');
+
+ _.keys(from.attrs || {}).forEach(function(key) {
+ node.attr(key, from.attrs[key]);
+ });
- from = node[0];
+ from = node[0];
+ }
}
}
var Factory, typeMethods, typeTransformations;
@@ -318,17 +346,22 @@ $.extend(Document.prototype, Backbone.Events, {
loadXML: function(xml, options) {
options = options || {};
this._defineDocumentProperties($(parseXML(xml)));
+ this.normalizeXML(this.dom);
if(!options.silent) {
this.trigger('contentSet');
}
},
+ normalizeXML: function(nativeNode) {
+ void(nativeNode); // noop
+ },
+
toXML: function() {
return this.root.toXML();
},
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) {
@@ -351,7 +384,6 @@ $.extend(Document.prototype, Backbone.Events, {
},
trigger: function() {
- //console.log('trigger: ' + arguments[0] + (arguments[1] ? ', ' + arguments[1].type : ''));
Backbone.Events.trigger.apply(this, arguments);
},
@@ -390,9 +422,7 @@ $.extend(Document.prototype, Backbone.Events, {
},
registerExtension: function(extension) {
- //debugger;
- var doc = this,
- existingPropertyNames = _.values(this);
+ var doc = this;
['document', 'documentNode', 'elementNode', 'textNode'].forEach(function(dstName) {
var dstExtension = extension[dstName];
@@ -418,12 +448,30 @@ $.extend(Document.prototype, Backbone.Events, {
});
},
+ 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) {
- //console.log('transform');
var toret, transformation;
- // ref: odrebnie przygotowanie transformacji, odrebnie jej wykonanie (to pierwsze to analog transform z node)
-
if(typeof Transformation === 'function') {
transformation = new Transformation(this, this, args);
} else {
@@ -431,33 +479,110 @@ $.extend(Document.prototype, Backbone.Events, {
}
if(transformation) {
this._transformationLevel++;
- toret = transformation.run();
- if(this._transformationLevel === 1) {
- this.undoStack.push(transformation);
- }
+
+ 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--;
- //console.log('clearing redo stack');
- this.redoStack = [];
return toret;
} else {
throw new Error('Transformation ' + transformation + ' doesn\'t exist!');
}
},
undo: function() {
- var transformation = this.undoStack.pop();
- if(transformation) {
- transformation.undo();
- this.redoStack.push(transformation);
+ 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];
+ }
+
+ 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.
+ stopAt = undefined;
+ transformations.some(function(t, idx) {
+ if(!t.undo && t.getChangeRoot().sameNode(doc.root)) {
+ stopAt = idx;
+ return true; //break
+ }
+ });
+ if(stopAt !== undefined) {
+ // We will get away with undoing only this transformations as the one at stopAt reverses the whole document.
+ transformations = transformations.slice(0, stopAt+1);
+ }
+ }
+
+ transformations.reverse();
+ transformations.forEach(function(t) {
+ t.undo();
+ });
+
+ this._undoInProgress = false;
+ this.redoStack.push(transformationObject);
}
},
redo: function() {
- var transformation = this.redoStack.pop();
- if(transformation) {
- transformation.run();
- this.undoStack.push(transformation);
+ var transformationObject = this.redoStack.pop(),
+ transformations;
+ if(transformationObject) {
+ this._transformationLevel++;
+ transformations = _.isArray(transformationObject) ? transformationObject : [transformationObject];
+ transformations.forEach(function(t) {
+ t.run({beUndoable: true});
+ });
+ this._transformationLevel--;
+ this.undoStack.push(transformationObject);
}
},
+ startTransaction: function() {
+ if(this._transactionInProgress) {
+ throw new Error('Nested transactions not supported!');
+ }
+ this._transactionInProgress = true;
+ },
+
+ endTransaction: function() {
+ if(!this._transactionInProgress) {
+ 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 = [];
+ }
+ },
+
+ transaction: function(callback, context) {
+ this.startTransaction();
+ callback.call(context);
+ this.endTransaction();
+ },
+
getNodeByPath: function(path) {
var toret = this.root;
path.forEach(function(idx) {
@@ -481,7 +606,6 @@ $.extend(Document.prototype, Backbone.Events, {
return {
documentFromXML: function(xml) {
var doc = new Document(xml);
- doc.registerExtension(coreTransformations);
return doc;
},