wlxml: metadata wip - adding/removing/editing, undo, cloning support
authorAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Fri, 17 Jan 2014 10:02:20 +0000 (11:02 +0100)
committerAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Fri, 17 Jan 2014 14:24:55 +0000 (15:24 +0100)
src/wlxml/extensions/metadata/metadata.js
src/wlxml/extensions/metadata/metadata.test.js
src/wlxml/wlxml.js

index 86bd8ee..54b3f96 100644 (file)
@@ -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);
+            }
+        }
     }
 };
 
index 5de573b..422a371 100644 (file)
@@ -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('<section></section>');
         expect(doc.root.getMetadata().length).to.equal(0);
     });
     it('allows to set metadata on an element node', function() {
-        var doc = getDocumentFromXML('<section></section>');
-
-        var row = doc.root.addMetadata({key: 'key', value: 'value'}),
+        var doc = getDocumentFromXML('<section></section>'),
             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('<section><metadata><dc:key>value</dc:key><dc:key>value</dc:key></metadata></section>'),
-    //         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('<section><metadata><dc:key>value</dc:key><dc:key>value</dc:key></metadata></section>'),
+            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('<section><metadata><dc:key>value</dc:key></metadata></section>'),
             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('<section></section>');
 
-        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('<section><metadata><dc:key>value</dc:key></metadata></section>');
-        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('<section><div></div><metadata><dc:key>value</dc:key></metadata><div></div></section>'),
+                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('<section>Alice<metadata></metadata> has a cat</section>'),
+                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('<section></section>'),
+                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('<section><metadata><dc:key>value</dc:key></metadata></section>'),
+                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('<section><metadata><dc:key>value</dc:key></metadata></section>'),
+                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('<section><metadata><dc:key>value</dc:key></metadata></section>'),
+                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');
+        });
+    });
+
+
+
 });
 
 
index 539d792..fe0636d 100644 (file)
@@ -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) {