From 16c90b3616f1645aab9fe284fd3b89641dfb42f2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 17 Jan 2014 11:02:20 +0100 Subject: [PATCH] wlxml: metadata wip - adding/removing/editing, undo, cloning support --- src/wlxml/extensions/metadata/metadata.js | 138 ++++++++++++++---- .../extensions/metadata/metadata.test.js | 105 ++++++++++--- src/wlxml/wlxml.js | 22 +-- 3 files changed, 209 insertions(+), 56 deletions(-) diff --git a/src/wlxml/extensions/metadata/metadata.js b/src/wlxml/extensions/metadata/metadata.js index 86bd8ee..54b3f96 100644 --- a/src/wlxml/extensions/metadata/metadata.js +++ b/src/wlxml/extensions/metadata/metadata.js @@ -3,57 +3,143 @@ define(function(require) { 'use strict'; var _ = require('libs/underscore'), + smartxmlTransformations = require('smartxml/transformations'), metadataKey = 'wlxml.metadata'; -var Row = function(key, value) { + +var Row = function(key, value, metadata) { this.key = key; this.value = value; + this.metadata = metadata; }; + _.extend(Row.prototype, { + ChangeProperty: smartxmlTransformations.createContextTransformation({ + impl: function(t, rowIndex, propName, value) { + var row = this.getMetadata().at(rowIndex); + t.rowIndex = rowIndex; + t.propName = propName; + t.oldValue = row[propName]; + row[propName] = value; + this.triggerChangeEvent('metadataChanged', {row:row}); + }, + undo: function(t) { + var row = this.getMetadata().at(t.rowIndex); + row[t.propName] = t.oldValue; + this.triggerChangeEvent('metadataChanged', {row:row}); + } + }), + setKey: function(key) { - this.key = key; + return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'key', key]); }, getKey: function() { return this.key; }, setValue: function(value) { - this.value = value; + return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'value', value]); }, getValue: function() { return this.value; + }, + remove: function() { + this.metadata.remove(this); + }, + getIndex: function() { + return this.metadata.indexOf(this); } }); -// var Metadata = function(node) { -// this._rows = []; -// } -// _.extend(Metadata.prototype, { -// forEach: function(callback) { -// this. -// } -// }) - -var methods = { - getMetadata: function() { - return this.getData(metadataKey) || []; - } +var Metadata = function(node) { + this._rows = []; + Object.defineProperty(this, 'length', { + get: function() { + return this._rows.length; + } + }); + this.node = node; }; -var transformations = { - addMetadata: function(desc) { - var metadata = this.getData(metadataKey) || [], - row = new Row(desc.key, desc.value); - metadata.push(row); - this.setData(metadataKey, metadata); - return row; +_.extend(Metadata.prototype, { + Add: smartxmlTransformations.createContextTransformation({ + impl: function(t, rowDesc) { + var metadata = this.getMetadata(), + row = new Row(rowDesc.key, rowDesc.value, metadata); + metadata._rows.push(row); + t.rowIdx = row.getIndex(); + this.triggerChangeEvent('metadataAdded', {row: row}); + return row; + }, + undo: function(t) { + this.getMetadata().at(t.rowIdx).remove(); + } + }), + + Remove: smartxmlTransformations.createContextTransformation({ + impl: function(t, rowIdx) { + var metadata = this.getMetadata(); + t.rowIdx = rowIdx; + t.row = metadata.at(rowIdx); + metadata._rows.splice(rowIdx, 1); + this.triggerChangeEvent('metadataRemoved', {row: t.row}); + }, + undo: function(t) { + var metadata = this.getMetadata(); + metadata._rows.splice(t.rowIdx, 0, new Row(t.row.getKey(), t.row.getValue(), metadata)); + } + }), + + forEach: function(callback) { + return this._rows.forEach(callback); + }, + add: function(rowDesc, options) { + var row; + options = _.extend({undoable: true}, options); + if(options.undoable) { + return this.node.transform(this.Add, [rowDesc]); + } else { + row = new Row(rowDesc.key, rowDesc.value, this); + this._rows.push(row); + return row; + } + }, + at: function(idx) { + return this._rows[idx]; + }, + indexOf: function(row) { + var idx = this._rows.indexOf(row); + if(idx !== -1) { + return idx; + } + return undefined; + }, + remove: function(row) { + var idx = this.indexOf(row); + if(typeof idx !== 'undefined') { + this.node.transform(this.Remove, [idx]); + } + }, + clone: function(node) { + var clone = new Metadata(node); + this._rows.forEach(function(row) { + clone._rows.push(new Row(row.getKey(), row.getValue(), clone)); + }); + return clone; } -}; +}); + return { elementNode: { - methods: methods, - transformations: transformations, + methods: { + getMetadata: function() { + if(!this.getData(metadataKey)) { + this.setData(metadataKey, new Metadata(this)); + } + return this.getData(metadataKey); + } + } } }; diff --git a/src/wlxml/extensions/metadata/metadata.test.js b/src/wlxml/extensions/metadata/metadata.test.js index 5de573b..422a371 100644 --- a/src/wlxml/extensions/metadata/metadata.test.js +++ b/src/wlxml/extensions/metadata/metadata.test.js @@ -10,48 +10,48 @@ var chai = require('libs/chai'), expect = chai.expect, $ = require('libs/jquery'); + var getDocumentFromXML = function(xml, options) { return wlxml.WLXMLDocumentFromXML(xml, options || {}); }; -describe.only('Metadata API', function() { +describe('Metadata API', function() { it('returns empty metadata for node without metadata', function() { var doc = getDocumentFromXML('
'); expect(doc.root.getMetadata().length).to.equal(0); }); it('allows to set metadata on an element node', function() { - var doc = getDocumentFromXML('
'); - - var row = doc.root.addMetadata({key: 'key', value: 'value'}), + var doc = getDocumentFromXML('
'), metadata = doc.root.getMetadata(); - + + var row = metadata.add({key: 'key', value: 'value'}); expect(metadata.length).to.equal(1); - expect(metadata[0]).to.equal(row, 'aaa'); - + expect(metadata.at(0)).to.equal(row); expect(row.getKey()).to.equal('key'); expect(row.getValue()).to.equal('value'); }); - // it('allows to remove specific metadata row', function() { - // var doc = getDocumentFromXML('
valuevalue
'), - // metadata = doc.root.getMetadata(); - // expect(metadata.length).to.equal(2); - // row.remove(); - // expect(metadata.length) - // expect(metadata[0].getValue()).to.equal('value'); - // }); + it('allows to remove specific metadata row', function() { + var doc = getDocumentFromXML('
valuevalue
'), + metadata = doc.root.getMetadata(); + + expect(metadata.length).to.equal(2); + metadata.at(0).remove(); + expect(metadata.length).to.equal(1); + expect(metadata.at(0).getValue()).to.equal('value'); + }); it('reads node\'s metadata from source of its metadata child node', function() { var doc = getDocumentFromXML('
value
'), metadata = doc.root.getMetadata(); expect(metadata.length).to.equal(1); - expect(metadata[0].getKey()).to.equal('key'); - expect(metadata[0].getValue()).to.equal('value'); + expect(metadata.at(0).getKey()).to.equal('key'); + expect(metadata.at(0).getValue()).to.equal('value'); }); it('serializes node\'s metadata to its metadata child node', function() { var doc = getDocumentFromXML('
'); - doc.root.addMetadata({key: 'key', value: 'value'}); + doc.root.getMetadata().add({key: 'key', value: 'value'}); var metadataNodes = $(doc.toXML()).children('metadata'), keyNodes = metadataNodes.children(); @@ -61,11 +61,74 @@ describe.only('Metadata API', function() { expect(keyNodes[0].tagName.toLowerCase()).to.equal('dc:key'); expect($(keyNodes[0]).text()).to.equal('value'); }); - it('doesn\'t show metadata node on nodes contents', function() { - var doc = getDocumentFromXML('
value
'); - expect(doc.root.contents()).to.have.length(0); + + describe('Hiding metadata nodes from document api', function() { + it('hides metadata nodes from document api', function() { + var doc = getDocumentFromXML('
value
'), + rootContents = doc.root.contents(); + expect(rootContents).to.have.length(2); + expect(rootContents[0].getTagName()).to.equal('div'); + expect(rootContents[1].getTagName()).to.equal('div'); + expect(rootContents[0].next().sameNode(rootContents[1])).to.equal(true); + expect(rootContents[1].prev().sameNode(rootContents[0])).to.equal(true); + }); + + it('merges adjacent text nodes', function() { + var doc = getDocumentFromXML('
Alice has a cat
'), + contents = doc.root.contents(); + expect(contents.length).to.equal(1); + expect(contents[0].getText()).to.equal('Alice has a cat'); + }); }); + describe('undo', function() { + it('undoes adding metadata', function() { + var doc = getDocumentFromXML('
'), + metadata = doc.root.getMetadata(); + metadata.add({key: 'k', value: 'v'}); + doc.undo(); + expect(metadata.length).to.equal(0); + doc.redo(); + expect(metadata.length).to.equal(1); + expect(metadata.at(0).getValue()).to.equal('v'); + }); + it('undoes changing metadata key', function() { + var doc = getDocumentFromXML('
value
'), + metadata = doc.root.getMetadata(), + row = metadata.at(0); + + row.setKey('key2'); + doc.undo(); + expect(row.getKey()).to.equal('key'); + doc.redo(); + expect(row.getKey()).to.equal('key2'); + }); + it('undoes changing metadata value', function() { + var doc = getDocumentFromXML('
value
'), + metadata = doc.root.getMetadata(), + row = metadata.at(0); + + row.setValue('value2'); + doc.undo(); + expect(row.getValue()).to.equal('value'); + doc.redo(); + expect(row.getValue()).to.equal('value2'); + }); + it('undoes removing metadata', function() { + var doc = getDocumentFromXML('
value
'), + metadata = doc.root.getMetadata(), + row = metadata.at(0); + + row.remove(); + doc.undo(); + expect(metadata.length).to.equal(1, 'undo brought back metadata'); + doc.redo(); + expect(metadata.length).to.equal(0, 'redo removed metadata'); + }); + }); + + + }); diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index 539d792..fe0636d 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -181,6 +181,18 @@ $.extend(WLXMLDocument.prototype, { var doc = this, prefixLength = 'dc:'.length; + $(nativeNode).find('metadata').each(function() { + var metadataNode = $(this), + owner = doc.createDocumentNode(metadataNode.parent()[0]), + metadata = owner.getMetadata(); + + metadataNode.children().each(function() { + metadata.add({key: (this.tagName).toLowerCase().substr(prefixLength), value: $(this).text()}, {undoable: false}); + }); + metadataNode.remove(); + }); + nativeNode.normalize(); + $(nativeNode).find(':not(iframe)').addBack().contents() .filter(function() {return this.nodeType === Node.TEXT_NODE;}) .each(function() { @@ -264,15 +276,7 @@ $.extend(WLXMLDocument.prototype, { el.replaceWith(document.createTextNode(text.transformed)); }); - $(nativeNode).find('metadata').each(function() { - var metadataNode = $(this), - owner = doc.createDocumentNode(metadataNode.parent()[0]); - - metadataNode.children().each(function() { - owner.addMetadata({key: (this.tagName).toLowerCase().substr(prefixLength), value: $(this).text()}); - }); - metadataNode.remove(); - }); + }, registerClassTransformation: function(Transformation, className) { -- 2.20.1