save caret position when ending a list
[fnpeditor.git] / src / wlxml / extensions / metadata / metadata.js
index 4136673..899306d 100644 (file)
@@ -3,37 +3,156 @@ define(function(require) {
 'use strict';
 
 var _ = require('libs/underscore'),
+    smartxmlTransformations = require('smartxml/transformations'),
     metadataKey = 'wlxml.metadata';
 
 
-var methods = {
-    getMetadata: function() {
-        return this.getData(metadataKey) || [];
-    }
+var Row = function(key, value, metadata) {
+    this.key = key || '';
+    this.value  = value || '';
+    this.metadata = metadata;
 };
 
-var transformations = {
-    addMetadataRow: function(row) {
-        this.setMetadataRow(null, row);
+_.extend(Row.prototype, {
+    ChangeProperty: smartxmlTransformations.createContextTransformation({
+        impl: function(t, rowIndex, propName, value) {
+            var row = this.getMetadata().at(rowIndex);
+            if(row.getValue() === value) {
+                return;
+            }
+            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) {
+        return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'key', key]);
+    },
+    getKey: function() {
+        return this.key;
     },
+    setValue: function(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 = [];
+    Object.defineProperty(this, 'length', {
+        get: function() {
+            return this._rows.length;
+        }
+    });
+    this.node = node;
+};
+
+_.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();
+        }
+    }),
 
-    setMetadataRow: function(index, row) {
-        var metadata = this.getData(metadataKey) || [];
-        if(typeof index !== 'number' || index > metadata.length - 1) {
-            metadata.push(row);
-            index = metadata.length - 1;
+    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(),
+                row = new Row(t.row.getKey(), t.row.getValue(), metadata);
+            metadata._rows.splice(t.rowIdx, 0, row);
+            this.triggerChangeEvent('metadataAdded', {row: row});
+        }
+    }),
+
+    _iter: function(method, callback, key) {
+        return this._rows
+            .filter(function(row) { return !key || row.getKey() === key; })
+            [method](function(row) { return callback(row); });
+    },
+    forEach: function(callback, key) {
+        return this._iter('forEach', callback, key);
+    },
+    some: function(callback, key) {
+        return this._iter('some', callback, key);
+    },
+    add: function(rowDesc, options) {
+        var row;
+        options = _.extend({undoable: true}, options);
+        if(options.undoable) {
+            return this.node.transform(this.Add, [rowDesc]);
         } else {
-            metadata[index] = _.extend(metadata[index], row);
+            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;
         }
-        this.setData(metadataKey, metadata);
-        this.triggerChangeEvent('metadataChange', {index: index});
+        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);
+            }
+        }
     }
 };