X-Git-Url: https://git.mdrn.pl/fnpeditor.git/blobdiff_plain/6757e2f4fe80bfdc197d4963c464bf2b3a47f66b..0f5cbf957bc0bbdcff93bdb33c227f78041e332b:/src/smartxml/smartxml.js diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 1379153..9215287 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -2,8 +2,9 @@ define([ 'libs/jquery', 'libs/underscore', 'libs/backbone', - 'smartxml/events' -], function($, _, Backbone, events) { + 'smartxml/events', + 'smartxml/transformations' +], function($, _, Backbone, events, transformations) { 'use strict'; /* globals Node */ @@ -14,25 +15,18 @@ var TEXT_NODE = Node.TEXT_NODE; var INSERTION = function(implementation) { var toret = function(node) { var insertion = this.getNodeInsertion(node), + nodeWasContained = this.document.containsNode(insertion.ofNode), nodeParent; if(!(this.document.containsNode(this))) { nodeParent = insertion.ofNode.parent(); } implementation.call(this, insertion.ofNode.nativeNode); - this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent); + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent, nodeWasContained); return insertion.ofNode; }; return toret; }; -// var TRANSFORMATION = function(name, implementation) { -// //implementation._isTransformation = true; - -// createDumbTransformation(name, implementation, ) - -// return implementation; -// }; - var DocumentNode = function(nativeNode, document) { if(!document) { throw new Error('undefined document for a node'); @@ -45,7 +39,7 @@ var DocumentNode = function(nativeNode, document) { $.extend(DocumentNode.prototype, { transform: function(name, args) { - var Transformation = contextTransformations[name], + var Transformation = this.transformations.get(name), transformation; if(Transformation) { transformation = new Transformation(this.document, this, args); @@ -185,13 +179,13 @@ $.extend(DocumentNode.prototype, { } }, - triggerChangeEvent: function(type, metaData, origParent) { + 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)) { this.document.trigger('change', event); } - if((type === 'nodeAdded' || type === 'nodeMoved') && !(this.document.containsNode(this))) { + if((type === 'nodeAdded' || type === 'nodeMoved') && !this.document.containsNode(this) && nodeWasContained) { event = new events.ChangeEvent('nodeDetached', {node: node, parent: origParent}); this.document.trigger('change', event); } @@ -247,12 +241,18 @@ $.extend(ElementNode.prototype, { return this.nativeNode.tagName.toLowerCase(); }, - contents: function() { + contents: function(selector) { var toret = [], document = this.document; - this._$.contents().each(function() { - toret.push(document.createDocumentNode(this)); - }); + if(selector) { + this._$.children(selector).each(function() { + toret.push(document.createDocumentNode(this)); + }); + } else { + this._$.contents().each(function() { + toret.push(document.createDocumentNode(this)); + }); + } return toret; }, @@ -380,6 +380,63 @@ $.extend(ElementNode.prototype, { } }); +// trans + +// todo - split+append + +// ElementNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.setAttr', +// impl: function(args) { +// this.setAttr(args.name, args.value); +// }, +// getChangeRoot: function() { +// return this.context; +// } +// })); + +// ElementNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.setAttr2', +// impl: function(args) { +// this.prevAttr = this.getAttr(args.name); +// this.setAttr(args.name, args.value); +// }, +// undo: function(args) { +// this.setAttr(args.name, this.prevAttr); +// } +// })); + +// DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.wrapWith', +// getChangeRoot: function() { +// return this.context.parent(); +// }, +// impl: function(args) { +// return this.wrapWith(args); +// } +// })); + +// DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.wrapText', +// getChangeRoot: function() { +// return this.context; +// }, +// impl: function(args) { +// return this.wrapText(args); +// } +// })); + +// DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.detach', +// getChangeRoot: function() { +// return this.context.parent(); +// }, +// impl: function(args) { +// return this.detach(); +// } +// })); + +/// + var TextNode = function(nativeNode, document) { DocumentNode.call(this, nativeNode, document); }; @@ -393,7 +450,7 @@ $.extend(TextNode.prototype, { }, setText: function(text) { - console.log('smartxml: ' + text); + //console.log('smartxml: ' + text); this.nativeNode.data = text; this.triggerTextChangeEvent(); }, @@ -467,6 +524,53 @@ $.extend(TextNode.prototype, { }); +// TextNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'rng.breakContent', +// // impl: function(args) { +// // var node = this.context, +// // newNodes, emptyNode, emptyText; +// // newNodes = node.transform('smartxml.split', {offset: args.offset}); +// // [newNodes.first, newNodes.second].some(function(newNode) { +// // if(!(newNode.contents().length)) { +// // newNode.transform('smartxml.append', {text: ''}); +// // return true; // break +// // } +// // }); +// // return _.extend(newNodes, {emptyText: emptyText}); +// // }, +// impl: function(args) { +// var node = this, +// newNodes, emptyNode, emptyText; +// newNodes = node.split({offset: args.offset}); +// [newNodes.first, newNodes.second].some(function(newNode) { +// if(!(newNode.contents().length)) { +// newNode.append({text: ''}); +// return true; // break +// } +// }); +// return _.extend(newNodes, {emptyText: emptyText}); +// }, +// getChangeRoot: function() { +// return this.context.parent().parent(); +// }, +// isAllowed: function(args) { +// var parent = this.parent(); +// return !!(parent && parent.parent()); +// } +// })); + + +// ElementNode.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.setText', +// impl: function(args) { +// this.setText(args.text); +// }, +// getChangeRoot: function() { +// return this.context; +// } +// })); + + var parseXML = function(xml) { return $($.trim(xml))[0]; }; @@ -475,6 +579,11 @@ var Document = function(xml) { this.loadXML(xml); this.undoStack = []; this.redoStack = []; + this._transformationLevel = 0; + this.transformations = new transformations.TransformationStorage(); + + this._nodeMethods = {}; + this._nodeTransformations = new transformations.TransformationStorage(); }; $.extend(Document.prototype, Backbone.Events, { @@ -502,7 +611,10 @@ $.extend(Document.prototype, Backbone.Events, { } else if(from.nodeType === Node.ELEMENT_NODE) { Factory = this.ElementNodeFactory; } - return new Factory(from, this); + var toret = new Factory(from, this); + _.extend(toret, this._nodeMethods); + toret.transformations = this._nodeTransformations; + return toret; }, loadXML: function(xml, options) { @@ -651,25 +763,107 @@ $.extend(Document.prototype, Backbone.Events, { return insertion.ofNode; }, - transform: function(transformationName, args) { - console.log('transform'); - var Transformation, transformation, toret; - if(typeof transformationName === 'string') { - Transformation = transformations[transformationName]; + registerMethod: function(methodName, method) { + this[methodName] = method; + }, + + registerTransformation: function(Transformation) { + return this.transformations.register(Transformation); + }, + + registerNodeMethod: function(methodName, method) { + this._nodeMethods[methodName] = method; + }, + + registerNodeTransformation: function(Transformation) { + this._nodeTransformations.register(Transformation); + }, + + registerExtension: function(extension) { + //debugger; + var doc = this, + existingPropertyNames = _.values(this); + + var getTrans = function(desc, methodName) { + if(typeof desc === 'function') { + desc = {impl: desc}; + } + if(!desc.impl) { + throw new Error('Got transformation description without implementation.') + } + desc.name = desc.name || methodName; + return desc; + }; + + [ + {source: extension.document, target: doc}, + {source: extension.documentNode, target: [doc.ElementNodeFactory.prototype, doc.TextNodeFactory.prototype]}, + + ].forEach(function(mapping) { + if(mapping.source) { + if(mapping.source.methods) { + existingPropertyNames = _.values(mapping.target) + _.pairs(mapping.source.methods).forEach(function(pair) { + var methodName = pair[0], + method = pair[1], + targets = _.isArray(mapping.target) ? mapping.target : [mapping.target]; + if(_.contains(existingPropertyNames, methodName)) { + throw new Error('Cannot extend {target} with method name {methodName}. Name already exists.' + .replace('{target}', mapping.target) + .replace('{methodName}', methodName) + ); + } + targets.forEach(function(target) { + if(target === doc) { + target.registerMethod(methodName, method); + } else { + doc.registerNodeMethod(methodName, method); + } + + }); + }); + } + + if(mapping.source.transformations) { + _.pairs(mapping.source.transformations).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]), + targets = _.isArray(mapping.target) ? mapping.target : [mapping.target]; + targets.forEach(function(target) { + if(target === doc) { + target.registerTransformation(transformations.createContextTransformation(transformation)); + } else { + doc.registerNodeTransformation(transformations.createContextTransformation(transformation)); + } + + + }); + }); + } + } + }); + }, + + transform: function(transformation, args) { + //console.log('transform'); + var Transformation, toret; + if(typeof transformation === 'string') { + Transformation = this.transformations.get(transformation); if(Transformation) { - transformation = new Transformation(args); + transformation = new Transformation(this, this, args); } - } else { - transformation = transformationName; - } + } if(transformation) { + this._transformationLevel++; toret = transformation.run(); - this.undoStack.push(transformation); - console.log('clearing redo stack'); + if(this._transformationLevel === 1) { + this.undoStack.push(transformation); + } + this._transformationLevel--; + //console.log('clearing redo stack'); this.redoStack = []; return toret; } else { - throw new Error('Transformation ' + transformationName + ' doesn\'t exist!'); + throw new Error('Transformation ' + transformation + ' doesn\'t exist!'); } }, undo: function() { @@ -705,314 +899,20 @@ var defineDocumentProperties = function(doc, $document) { }, configurable: true}); }; +// Document.prototype.transformations.register(transformations.createContextTransformation({ +// name: 'smartxml.wrapNodes', +// // init: function() { -// var registerTransformationsFromObject = function(object) { -// _.values(object).filter(function(val) { -// return typeof val === 'function' && val._isTransformation; -// }) -// .forEach(function(val) { -// registerTransformation(val._transformationName, val, object); -// }); -// }; -// registerTransformationsFromObject(ElementNode.prototype); -// registerTransformationsFromObject(TextNode.prototype); -// registerTransformationsFromObject(Document.prototype); - -// var Transformation = function() { -// }; -// $.extend(Transformation.prototype, { - -// }); - - -// var createDumbTransformation = function(impl, contextObject) { -// var DumbTransformation = function(args) { -// this.args = this.args; -// }; -// DumbTransformation.prototype = Object.create(Transformation.prototype); -// $.extend(DumbTransformation.prototype, { -// run: function() { -// impl.apply(contextObject, this.args); -// } -// }); - -// return DumbTransformation; - - -// }; - -var transformations = {}; -// var registerTransformation = function(name, impl, contextObject) { -// if(typeof impl === 'function') { -// transformations[name] = createDumbTransformation(impl, contextObject); -// } -// }; - -// registerTransformation('detachx', DocumentNode.prototype.detach, ) - - -// 1. detach via totalny fallback -var DetachNodeTransformation = function(args) { - this.node = args.node; - this.document = this.node.document; -}; -$.extend(DetachNodeTransformation.prototype, { - run: function() { - this.oldRoot = this.node.document.root.clone(); - this.path = this.node.getPath(); - this.node.detach(); // @TS - - }, - undo: function() { - this.document.root.replaceWith(this.oldRoot); // this.getDocument? - this.node = this.document.getNodeByPath(this.path); - } -}); -transformations['detach'] = DetachNodeTransformation; - -//2. detach via wskazanie changeroot - -var Detach2NodeTransformation = function(args) { - this.nodePath = args.node.getPath(); - this.document = args.node.document; -}; -$.extend(Detach2NodeTransformation.prototype, { - run: function() { - var node = this.document.getNodeByPath(this.nodePath), - root = node.parent() ? node.parent() : this.document.root; - - this.rootPath = root.getPath(); - this.oldRoot = (root).clone(); - node.detach(); - }, - undo: function() { - this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); - } -}); -//transformations['detach2'] = Detach2NodeTransformation; - -//2a. generyczna transformacja - -var createTransformation = function(desc) { - - var NodeTransformation = function(args) { - this.nodePath = args.node.getPath(); - this.document = args.node.document; - this.args = args; - }; - $.extend(NodeTransformation.prototype, { - run: function() { - var node = this.document.getNodeByPath(this.nodePath), - root; - - if(desc.getRoot) { - root = desc.getRoot(node); - } else { - root = this.document.root; - } - - this.rootPath = root.getPath(); - this.oldRoot = (root).clone(); - desc.impl.call(node, this.args); - }, - undo: function() { - this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); - } - }); - - return NodeTransformation; -} - -/// -var Transformation = function(args) { - this.args = args; -}; -$.extend(Transformation.prototype, { - run: function() { - throw new Error('not implemented'); - }, - undo: function() { - throw new Error('not implemented'); - }, -}); - -var createGenericTransformation = function(desc) { - var GenericTransformation = function(document, args) { - //document.getNodeByPath(contextPath).call(this, args); - this.args = args; - this.document = document; - }; - $.extend(GenericTransformation.prototype, { - run: function() { - var changeRoot = desc.getChangeRoot ? desc.getChangeRoot.call(this) : this.document.root; - this.snapshot = changeRoot.clone(); - this.changeRootPath = changeRoot.getPath(); - return desc.impl.call(this.context, this.args); // a argumenty do metody? - }, - undo: function() { - this.document.getNodeByPath(this.changeRootPath).replaceWith(this.snapshot); - }, - }); - - return GenericTransformation; -}; - -// var T = createGenericTransformation({impl: function() {}}); -// var t = T(doc, {a:1,b:2,c3:3}); - - -var createContextTransformation = function(desc) { - // mozna sie pozbyc przez przeniesienie object/context na koniec argumentow konstruktora generic transformation - var GenericTransformation = createGenericTransformation(desc); - - var ContextTransformation = function(document, object, args) { - var contextPath = object.getPath(); - - GenericTransformation.call(this, document, args); - - Object.defineProperty(this, 'context', { - get: function() { - return document.getNodeByPath(contextPath); - } - }); - } - ContextTransformation.prototype = Object.create(GenericTransformation.prototype); - return ContextTransformation; -} -// var T = createContextTransformation({impl: function() {}}); -// var t = T(doc, node, {a:1,b:2,c3:3}); -/// +// // }, +// // getChangeRoot: function() { +// // return this.context; +// // }, +// impl: function(args) { +// this.wrapNodes(args); +// }, -var contextTransformations = {}; -contextTransformations['setText'] = createContextTransformation({ - impl: function(args) { - this.setText(args.text); - }, - getChangeRoot: function() { - return this.context; - } -}); +// })); -contextTransformations['setAttr'] = createContextTransformation({ - impl: function(args) { - this.setAttr(args.name, args.value); - }, - getChangeRoot: function() { - return this.context; - } -}); - -contextTransformations['split'] = createContextTransformation({ - impl: function(args) { - return this.split({offset: args.offset}); - }//, - // getChangeRoot: function() { - // return this.context.parent().parent(); - // } -}); - -// var TRANSFORMATION2 = function(f, getChangeRoot, undo) { -// var context = this, - - -// var transformation = createContextTransformation({ -// impl: f, -// getChangeRoot: getChangeRoot, - -// }); - -// var toret = function() { -// var -// f.apply(context, createArgs ? createArgs(arguments) : arguments) -// }; -// return toret; -// } - -transformations['detach2'] = createTransformation({ - // impl: function() { - // //this.setAttr('class', 'cite'); // - // }, - impl: ElementNode.prototype.detach, - getRoot: function(node) { - return node.parent(); - } - -}); - -transformations['setText-old'] = createTransformation({ - impl: function(args) { - this.setText(args.text) - }, - getRoot: function(node) { - return node; - } - -}); - -transformations['setClass-old'] = createTransformation({ - impl: function(args) { - this.setClass(args.klass); - }, - getRoot: function(node) { - return node; - } -}) - -//3. detach z pełnym własnym redo - -var Detach3NodeTransformation = function(args) { - this.node = args.node; - this.document = this.node.document; -}; -$.extend(Detach3NodeTransformation.prototype, { - run: function() { - //this.index = this.node.getIndex(); - //this.parent = this.node.parent(); - - this.path = this.node.getPath(); - if(this.node.isSurroundedByTextElements()) { - this.prevText = this.node.prev().getText(); - this.nextText = this.node.next().getText(); - this.merge = true; - } else { - this.prevText = this.nextText = null; - this.merge = false; - } - - this.node.detach(); - }, - undo: function() { - var parent = this.document.getNodeByPath(this.path.slice(0,-1)), - idx = _.last(this.path); - var inserted = parent.insertAtIndex(this.node, idx); - if(this.merge) { - if(inserted.next()) { - inserted.before({text: this.prevText}); - inserted.next().setText(this.nextText); - } else { - inserted.prev().setText(this.prevText); - inserted.after({text: this.nextText}); - } - } - } -}); -transformations['detach3'] = Detach3NodeTransformation; - - -var registerTransformationsFromObject = function(object) { - _.pairs(object).filter(function(pair) { - var property = pair[1]; - return typeof property === 'function' && property._isTransformation; - }) - .forEach(function(pair) { - var name = pair[0], - method = pair[1]; - object.registerTransformation(name, createContextTransformation(method)); - }); -}; -registerTransformationsFromObject(ElementNode.prototype); -registerTransformationsFromObject(TextNode.prototype); -registerTransformationsFromObject(Document.prototype); return { documentFromXML: function(xml) { @@ -1025,7 +925,8 @@ return { Document: Document, DocumentNode: DocumentNode, - ElementNode: ElementNode + ElementNode: ElementNode, + TextNode: TextNode }; }); \ No newline at end of file