From b729636a8bfe248aa9890a6f3bef56cbbab885fd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 3 Dec 2013 16:31:28 +0100 Subject: [PATCH 1/1] extensions api wip - 1 canvas test fails because of setting setText trans via old api --- src/editor/modules/data/data.js | 4 +- src/smartxml/smartxml.js | 5 +- src/smartxml/transformations.js | 5 +- src/wlxml/extensions/list/list.js | 23 +++-- src/wlxml/extensions/list/list.test.js | 6 +- src/wlxml/extensions/uri.js | 11 ++- src/wlxml/wlxml.js | 123 +++++++++++++++++++++++-- src/wlxml/wlxml.test.js | 93 +++++++++++++++++++ 8 files changed, 242 insertions(+), 28 deletions(-) diff --git a/src/editor/modules/data/data.js b/src/editor/modules/data/data.js index 556d9b5..f3494a0 100644 --- a/src/editor/modules/data/data.js +++ b/src/editor/modules/data/data.js @@ -4,7 +4,7 @@ define([ 'wlxml/wlxml', 'wlxml/extensions/list/list' -], function($, saveDialog, wlxml) { +], function($, saveDialog, wlxml, listExtension) { 'use strict'; @@ -16,6 +16,8 @@ return function(sandbox) { var history = sandbox.getBootstrappedData().history; var wlxmlDocument = wlxml.WLXMLDocumentFromXML(sandbox.getBootstrappedData().document); + + wlxmlDocument.registerExtension(listExtension); function readCookie(name) { diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 6cc3e33..bb47c06 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -581,6 +581,8 @@ var Document = function(xml) { this.undoStack = []; this.redoStack = []; this._transformationLevel = 0; + this.transformations = new transformations.TransformationStorage(); + }; $.extend(Document.prototype, Backbone.Events, { @@ -840,7 +842,8 @@ return { Document: Document, DocumentNode: DocumentNode, - ElementNode: ElementNode + ElementNode: ElementNode, + TextNode: TextNode }; }); \ No newline at end of file diff --git a/src/smartxml/transformations.js b/src/smartxml/transformations.js index 53dc384..8cf0819 100644 --- a/src/smartxml/transformations.js +++ b/src/smartxml/transformations.js @@ -95,10 +95,11 @@ toret.createContextTransformation = function(desc) { -toret.TransformationStorage = function() {}; +toret.TransformationStorage = function() { + this._transformations = {}; +}; _.extend(toret.TransformationStorage.prototype, { - _transformations: {}, register: function(Transformation) { var list = (this._transformations[Transformation.prototype.name] = this._transformations[Transformation.prototype.name] || []); diff --git a/src/wlxml/extensions/list/list.js b/src/wlxml/extensions/list/list.js index 4291957..75b5336 100644 --- a/src/wlxml/extensions/list/list.js +++ b/src/wlxml/extensions/list/list.js @@ -1,13 +1,11 @@ -define(function(require) { +define(function() { 'use strict'; +var extension = {document: {transformations: {}}, wlxmlClass: {list: {methods: {}}}}; -var wlxml = require('wlxml/wlxml'), - extension = {documentTransformations: [], classMethods: {}}; - -extension.classMethods['list'] = { +extension.wlxmlClass.list.methods = { itemIndex: function(listItem) { var toret = -1; this.contents('.item').some(function(item, index) { @@ -29,8 +27,9 @@ extension.documentMethods = { } } -extension.documentTransformations.push({ - name: 'createList', + + +extension.document.transformations.createList = { impl: function(params) { var parent = params.node1.parent(), parentContents = parent.contents(), @@ -71,10 +70,9 @@ extension.documentTransformations.push({ isAllowed: function() { return this.args.node1.parent().sameNode(this.args.node2.parent()); } -}); +}; -extension.documentTransformations.push({ - name: 'extractItems', +extension.document.transformations.extractItems = { impl: function(params) { params = _.extend({}, {merge: true}, params); var list = params.item1.parent(), @@ -145,8 +143,9 @@ extension.documentTransformations.push({ var parent = this.args.nodel1.parent(); return parent.is('list') && parent.sameNode(this.args.node2.parent()); } -}); +}; + +return extension; -wlxml.registerExtension(extension); }); \ No newline at end of file diff --git a/src/wlxml/extensions/list/list.test.js b/src/wlxml/extensions/list/list.test.js index 1efa795..10a6e91 100644 --- a/src/wlxml/extensions/list/list.test.js +++ b/src/wlxml/extensions/list/list.test.js @@ -6,11 +6,13 @@ var chai = require('libs/chai'), wlxml = require('wlxml/wlxml'), expect = chai.expect, $ = require('libs/jquery'), - lists = require('wlxml/extensions/list/list'); + listsExtension = require('wlxml/extensions/list/list'); var getDocumentFromXML = function(xml, options) { - return wlxml.WLXMLDocumentFromXML(xml, options || {}); + var doc = wlxml.WLXMLDocumentFromXML(xml, options || {}); + doc.registerExtension(listsExtension); + return doc; }; var removeEmptyTextNodes = function(xml) { diff --git a/src/wlxml/extensions/uri.js b/src/wlxml/extensions/uri.js index ec8f939..88e3f66 100644 --- a/src/wlxml/extensions/uri.js +++ b/src/wlxml/extensions/uri.js @@ -2,9 +2,12 @@ define(function() { 'use strict'; -return { - className: 'uri', - attributes: {uri: {type: 'string'}} -}; +var extenstion = {wlxmlClass: {uri: attributes: { + { + uri: {type: 'string'} + } +}}} + +return extension; }); \ No newline at end of file diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index dbc0cf0..ee85a03 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -22,9 +22,19 @@ AttributesList.prototype.keys = function() { }; var installObject = function(instance, klass) { - var methods = classMethods[klass]; + var methods = instance.document.classMethods[klass]; if(methods) { - instance.object = Object.create(methods); + instance.object = Object.create(_.extend({ + transform: function(name, args) { + // TODO: refactor with DocumentElement.transform + var Transformation = instance.document.classTransformations[klass].get(name), + transformation; + if(Transformation) { + transformation = new Transformation(instance.document, instance, args); + } + return instance.document.transform(transformation); + } + }, methods)); _.keys(methods).forEach(function(key) { instance.object[key] = _.bind(instance.object[key], instance); }); @@ -139,17 +149,43 @@ WLXMLElementNode.prototype.transformations.register(transformations.createContex +var WLXMLDocumentNode = function() { + smartxml.DocumentNode.apply(this, arguments); +} +WLXMLDocumentNode.prototype = Object.create(smartxml.DocumentNode.prototype); + var WLXMLDocument = function(xml, options) { smartxml.Document.call(this, xml); this.options = options; + + // this.DocumentNodeFactory = function() { + // WLXMLDocumentNode.apply(this, arguments); + // }; + + // this.DocumentNodeFactory.prototype = Object.create(WLXMLDocumentNode.prototype); + + this.ElementNodeFactory = function() { + WLXMLElementNode.apply(this, arguments); + } + this.ElementNodeFactory.prototype = Object.create(WLXMLElementNode.prototype); + this.ElementNodeFactory.prototype.transformations = new transformations.TransformationStorage(); + + this.TextNodeFactory = function() { + smartxml.TextNode.apply(this, arguments); + } + this.TextNodeFactory.prototype = Object.create(smartxml.TextNode.prototype); + this.TextNodeFactory.prototype.transformations = new transformations.TransformationStorage(); + + this.classMethods = {}; + this.classTransformations = {}; }; var formatter_prefix = '_wlxml_formatter_'; + WLXMLDocument.prototype = Object.create(smartxml.Document.prototype); $.extend(WLXMLDocument.prototype, { ElementNodeFactory: WLXMLElementNode, - loadXML: function(xml) { smartxml.Document.prototype.loadXML.call(this, xml, {silent: true}); $(this.dom).find(':not(iframe)').addBack().contents() @@ -235,6 +271,78 @@ $.extend(WLXMLDocument.prototype, { el.replaceWith(document.createTextNode(text.transformed)); }); this.trigger('contentSet'); + }, + + registerExtension: function(extension) { + //debugger; + var doc = this, + existingPropertyName = _.values(this); + + [ + {source: extension.document, target: doc}, + {source: extension.documentNode, target: [doc.ElementNodeFactory.prototype, doc.TextNodeFactory.prototype]}, + + ].forEach(function(x) { + if(x.source && x.source.methods) { + existingPropertyName = _.values(x.target) + _.pairs(x.source.methods).forEach(function(pair) { + var methodName = pair[0], + method = pair[1], + targets = _.isArray(x.target) ? x.target : [x.target]; + if(_.contains(existingPropertyName, methodName)) { + throw new Error('Cannot extend XXX with method name {methodName}. Name already exists.'.replace('{methodName}', methodName)); + } + targets.forEach(function(target) { + target[methodName] = method; + }); + + }); + } + }); + + + 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; + }; + + if(extension.document && extension.document.transformations) { + _.pairs(extension.document.transformations).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]); + doc.transformations.register(transformations.createContextTransformation(transformation)); + }); + } + + if(extension.documentNode && extension.documentNode.transformations) { + _.pairs(extension.documentNode.transformations).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]); + + doc.ElementNodeFactory.prototype.transformations.register(transformations.createContextTransformation(transformation)); + doc.TextNodeFactory.prototype.transformations.register(transformations.createContextTransformation(transformation)); + }); + } + + _.pairs(extension.wlxmlClass).forEach(function(pair) { + var className = pair[0], + classExtension = pair[1], + thisClassMethods = (doc.classMethods[className] = doc.classMethods[className] || {}), + thisClassTransformations = (doc.classTransformations[className] = doc.classTransformations[className] || new transformations.TransformationStorage()); + + _.extend(thisClassMethods, classExtension.methods || {}); //@ warning/throw on override? + + + _.pairs(classExtension.transformations || {}).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]); + thisClassTransformations.register(transformations.createContextTransformation(transformation)); + }); + }); + } }); @@ -258,9 +366,12 @@ return { }, registerExtension: function(extension) { - extension.documentTransformations.forEach(function(method) { - WLXMLDocument.prototype.transformations.register(transformations.createContextTransformation(method)); - }); + // @@ depracated + if(extension.documentTransformations) { + extension.documentTransformations.forEach(function(method) { + WLXMLDocument.prototype.transformations.register(transformations.createContextTransformation(method)); + }); + } _.pairs(extension.classMethods).forEach(function(pair) { var className = pair[0], diff --git a/src/wlxml/wlxml.test.js b/src/wlxml/wlxml.test.js index c802ae3..e2c11fa 100644 --- a/src/wlxml/wlxml.test.js +++ b/src/wlxml/wlxml.test.js @@ -251,6 +251,99 @@ describe('WLXMLDocument', function() { }); + describe('Extension', function() { + var doc, extension, elementNode, textNode, testClassNode; + + beforeEach(function() { + doc = getDocumentFromXML('
Alice
'); + elementNode = doc.root; + textNode = doc.root.contents()[0]; + extension = {}; + + console.log('A'); + expect(function() { + elementNode.transform('testTransformation'); + }).to.throw(Error); + console.log('B'); + expect(function() { + textNode.transform('testTransformation'); + }).to.throw(Error); + console.log('C'); + expect(function() { + doc.transform('testTransformation'); + }).to.throw(Error); + expect(doc.testMethod).to.be.undefined; + expect(elementNode.testMethod).to.be.undefined; + expect(textNode.testMethod).to.be.undefined; + }); + + it('allows adding method to a document', function() { + extension = {document: {methods: { + testMethod: function() { return this; } + }}}; + + doc.registerExtension(extension); + expect(doc.testMethod()).to.equal(doc, 'context is set to a document instance'); + }); + + it('allows adding transformation to a document', function() { + extension = {document: {transformations: { + testTransformation: function() { return this; }, + testTransformation2: {impl: function() { return this;}} + }}}; + + doc.registerExtension(extension); + expect(doc.transform('testTransformation')).to.equal(doc, 'context is set to a document instance'); + expect(doc.transform('testTransformation2')).to.equal(doc, 'context is set to a document instance'); + }); + + it('allows adding method to a DocumentNode instance', function() { + extension = {documentNode: {methods: { + testMethod: function() { return this; } + }}}; + + doc.registerExtension(extension); + expect(elementNode.testMethod().sameNode(elementNode)).to.equal(true, 'context is set to a node instance'); + expect(textNode.testMethod().sameNode(textNode)).to.equal(true, 'context is set to a node instance'); + }); + + it('allows adding transformation to a DocumentNode', function() { + extension = {documentNode: {transformations: { + testTransformation: function() { return this; }, + testTransformation2: {impl: function() { return this;}} + }}}; + + doc.registerExtension(extension); + + expect(elementNode.transform('testTransformation').sameNode(elementNode)).to.equal(true, '1'); + expect(elementNode.transform('testTransformation2').sameNode(elementNode)).to.equal(true, '2'); + expect(textNode.transform('testTransformation').sameNode(textNode)).to.equal(true, '3'); + expect(textNode.transform('testTransformation2').sameNode(textNode)).to.equal(true, '4'); + }); + + it('allows adding method to an ElementNode of specific class', function() { + extension = {wlxmlClass: {test_class: {methods: { + testMethod: function() { return this; } + }}}}; + doc.registerExtension(extension); + testClassNode = doc.root.contents()[1]; + expect(testClassNode.object.testMethod().sameNode(testClassNode)).to.equal(true, '1'); + }); + + it('allows adding transformation to an ElementNode of specific class', function() { + extension = {wlxmlClass: {test_class: {transformations: { + testTransformation: function() { return this; }, + testTransformation2: {impl: function() { return this; }} + }}}}; + doc.registerExtension(extension); + testClassNode = doc.root.contents()[1]; + expect(testClassNode.object.transform('testTransformation').sameNode(testClassNode)).to.equal(true, '1'); + expect(testClassNode.object.transform('testTransformation2').sameNode(testClassNode)).to.equal(true, '1'); + }); + + + }); + }); }); \ No newline at end of file -- 2.20.1