X-Git-Url: https://git.mdrn.pl/fnpeditor.git/blobdiff_plain/87b603a0cc0a310b8166d8126e84259096a7a83d..edaa08a1858371fbdd9665a0220cb1b9732dd2dc:/src/smartxml/smartxml.js?ds=sidebyside diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 426dce0..cfae7c7 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -4,8 +4,9 @@ define([ 'libs/backbone', 'smartxml/events', 'smartxml/transformations', - 'smartxml/core' -], function($, _, Backbone, events, transformations, coreTransformations) { + 'smartxml/core', + 'smartxml/fragments' +], function($, _, Backbone, events, transformations, coreTransformations, fragments) { 'use strict'; /* globals Node */ @@ -78,6 +79,14 @@ $.extend(DocumentNode.prototype, { return this.document.root.sameNode(this); }, + isInDocument: function() { + return this.document.containsNode(this); + }, + + isSiblingOf: function(node) { + return node && this.parent().sameNode(node.parent()); + }, + sameNode: function(otherNode) { return !!(otherNode) && this.nativeNode === otherNode.nativeNode; }, @@ -113,22 +122,27 @@ $.extend(DocumentNode.prototype, { return myIdx < parentContents.length - 1 ? parentContents[myIdx+1] : null; }, - isSurroundedByTextElements: function() { - var prev = this.prev(), - next = this.next(); - return prev && (prev.nodeType === Node.TEXT_NODE) && next && (next.nodeType === Node.TEXT_NODE); + isSurroundedByTextNodes: function() { + return this.isPrecededByTextNode() && this.isFollowedByTextNode(); + }, + + isPrecededByTextNode: function() { + var prev = this.prev(); + return prev && prev.nodeType === Node.TEXT_NODE; + }, + + isFollowedByTextNode: function() { + var next = this.next(); + return next && next.nodeType === Node.TEXT_NODE; }, triggerChangeEvent: function(type, metaData, origParent, nodeWasContained) { 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) { + if(type === 'nodeAdded' && !this.document.containsNode(this) && nodeWasContained) { event = new events.ChangeEvent('nodeDetached', {node: node, parent: origParent}); this.document.trigger('change', event); } @@ -143,6 +157,10 @@ $.extend(DocumentNode.prototype, { return 0; } return this.parent().indexOf(this); + }, + + getNearestElementNode: function() { + return this.nodeType === Node.ELEMENT_NODE ? this : this.parent(); } }); @@ -155,12 +173,16 @@ ElementNode.prototype = Object.create(DocumentNode.prototype); $.extend(ElementNode.prototype, { nodeType: Node.ELEMENT_NODE, - setData: function(key, value) { - if(value !== undefined) { - this._$.data(key, value); + setData: function(arg1, arg2) { + if(arguments.length === 2) { + if(_.isUndefined(arg2)) { + this._$.removeData(arg1); + } else { + this._$.data(arg1, arg2); + } } else { this._$.removeData(_.keys(this._$.data())); - this._$.data(key); + this._$.data(arg1); } }, @@ -210,6 +232,22 @@ $.extend(ElementNode.prototype, { return node && (node.nativeNode === this.nativeNode || node._$.parents().index(this._$) !== -1); }, + getLastTextNode: function() { + var contents = this.contents(), + toret; + + contents.reverse().some(function(node) { + if(node.nodeType === Node.TEXT_NODE) { + toret = node; + return true; + } + toret = node.getLastTextNode(); + return !!toret; + }); + + return toret; + }, + toXML: function() { var wrapper = $('
'); wrapper.append(this._getXMLDOMToDump()); @@ -234,6 +272,11 @@ $.extend(TextNode.prototype, { return this.nativeNode.data; }, + + containsNode: function() { + return false; + }, + triggerTextChangeEvent: function() { var event = new events.ChangeEvent('nodeTextChange', {node: this}); this.document.trigger('change', event); @@ -291,7 +334,7 @@ var Document = function(xml, extensions) { this.loadXML(xml); }; -$.extend(Document.prototype, Backbone.Events, { +$.extend(Document.prototype, Backbone.Events, fragments, { ElementNodeFactory: ElementNode, TextNodeFactory: TextNode, @@ -479,7 +522,7 @@ $.extend(Document.prototype, Backbone.Events, { if(!this._currentTransaction) { return this.transaction(function() { return this.transform(Transformation, args); - }, this); + }, {context: this}); } if(typeof Transformation === 'function') { @@ -542,6 +585,7 @@ $.extend(Document.prototype, Backbone.Events, { this._undoInProgress = false; this.redoStack.push(transaction); + this.trigger('operationEnd'); } }, redo: function() { @@ -553,6 +597,8 @@ $.extend(Document.prototype, Backbone.Events, { }); this._transformationLevel--; this.undoStack.push(transaction); + this.trigger('operationEnd'); + } }, @@ -560,6 +606,7 @@ $.extend(Document.prototype, Backbone.Events, { if(this._currentTransaction) { throw new Error('Nested transactions not supported!'); } + this._rollbackBackup = this.root.clone(); this._currentTransaction = new Transaction([], metadata); }, @@ -569,15 +616,38 @@ $.extend(Document.prototype, Backbone.Events, { } if(this._currentTransaction.hasTransformations()) { this.undoStack.push(this._currentTransaction); + this.trigger('operationEnd'); + } + this._currentTransaction = null; + }, + + rollbackTransaction: function() { + if(!this._currentTransaction) { + throw new Error('Transaction rollback requested, but there is no transaction in progress!'); } + this.replaceRoot(this._rollbackBackup); + this._rollbackBackup = null; this._currentTransaction = null; + this._transformationLevel = 0; }, - transaction: function(callback, context, metadata) { + transaction: function(callback, params) { var toret; - this.startTransaction(metadata); - toret = callback.call(context); + params = params || {}; + this.startTransaction(params.metadata); + try { + toret = callback.call(params.context || this); + } catch(e) { + if(params.error) { + params.error(e); + } + this.rollbackTransaction(); + return; + } this.endTransaction(); + if(params.success) { + params.success(toret); + } return toret; }, @@ -603,6 +673,13 @@ $.extend(Document.prototype, Backbone.Events, { } return $document[0]; }, configurable: true}); + }, + + createFragment: function(Type, params) { + if(!Type.prototype instanceof fragments.Fragment) { + throw new Error('Can\'t create a fragment: `Type` is not a valid Fragment'); + } + return new Type(this, params); } });