smartxml: caching text nodes via expando
[fnpeditor.git] / src / wlxml / extensions / metadata / metadata.js
1 define(function(require) {
2     
3 'use strict';
4
5 var _ = require('libs/underscore'),
6     smartxmlTransformations = require('smartxml/transformations'),
7     metadataKey = 'wlxml.metadata';
8
9
10 var Row = function(key, value, metadata) {
11     this.key = key || '';
12     this.value  = value || '';
13     this.metadata = metadata;
14 };
15
16 _.extend(Row.prototype, {
17     ChangeProperty: smartxmlTransformations.createContextTransformation({
18         impl: function(t, rowIndex, propName, value) {
19             var row = this.getMetadata().at(rowIndex);
20             if(row.getValue() === value) {
21                 return;
22             }
23             t.rowIndex = rowIndex;
24             t.propName = propName;
25             t.oldValue = row[propName];
26             row[propName] = value;
27             this.triggerChangeEvent('metadataChanged', {row:row});
28         },
29         undo: function(t) {
30             var row = this.getMetadata().at(t.rowIndex);
31             row[t.propName] = t.oldValue;
32             this.triggerChangeEvent('metadataChanged', {row:row});
33         }
34     }),
35
36     setKey: function(key) {
37         return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'key', key]);
38     },
39     getKey: function() {
40         return this.key;
41     },
42     setValue: function(value) {
43         return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'value', value]);
44     },
45     getValue: function() {
46         return this.value;
47     },
48     remove: function() {
49         this.metadata.remove(this);
50     },
51     getIndex: function() {
52         return this.metadata.indexOf(this);
53     }
54 });
55
56
57 var Metadata = function(node) {
58     this._rows = [];
59     Object.defineProperty(this, 'length', {
60         get: function() {
61             return this._rows.length;
62         }
63     });
64     this.node = node;
65 };
66
67 _.extend(Metadata.prototype, {
68     Add: smartxmlTransformations.createContextTransformation({
69         impl: function(t, rowDesc) {
70             var metadata = this.getMetadata(),
71                 row = new Row(rowDesc.key, rowDesc.value, metadata);
72             metadata._rows.push(row);
73             t.rowIdx = row.getIndex();
74             this.triggerChangeEvent('metadataAdded', {row: row});
75             return row;
76         },
77         undo: function(t) {
78             this.getMetadata().at(t.rowIdx).remove();
79         }
80     }),
81
82     Remove: smartxmlTransformations.createContextTransformation({
83         impl: function(t, rowIdx) {
84             var metadata = this.getMetadata();
85             t.rowIdx = rowIdx;
86             t.row = metadata.at(rowIdx);
87             metadata._rows.splice(rowIdx, 1);
88             this.triggerChangeEvent('metadataRemoved', {row: t.row});
89         },
90         undo: function(t) {
91             var metadata = this.getMetadata(),
92                 row = new Row(t.row.getKey(), t.row.getValue(), metadata);
93             metadata._rows.splice(t.rowIdx, 0, row);
94             this.triggerChangeEvent('metadataAdded', {row: row});
95         }
96     }),
97
98     _iter: function(method, callback, key) {
99         return this._rows
100             .filter(function(row) { return !key || row.getKey() === key; })
101             [method](function(row) { return callback(row); });
102     },
103     forEach: function(callback, key) {
104         return this._iter('forEach', callback, key);
105     },
106     some: function(callback, key) {
107         return this._iter('some', callback, key);
108     },
109     add: function(rowDesc, options) {
110         var row;
111         options = _.extend({undoable: true}, options);
112         if(options.undoable) {
113             return this.node.transform(this.Add, [rowDesc]);
114         } else {
115             row = new Row(rowDesc.key, rowDesc.value, this);
116             this._rows.push(row);
117             return row;
118         }
119     },
120     at: function(idx) {
121         return this._rows[idx];
122     },
123     indexOf: function(row) {
124         var idx = this._rows.indexOf(row);
125         if(idx !== -1) {
126             return idx;
127         }
128         return undefined;
129     },
130     remove: function(row) {
131         var idx = this.indexOf(row);
132         if(typeof idx !== 'undefined') {
133             this.node.transform(this.Remove, [idx]);
134         }
135     },
136     clone: function(node) {
137         var clone = new Metadata(node);
138         this._rows.forEach(function(row) {
139             clone._rows.push(new Row(row.getKey(), row.getValue(), clone));
140         });
141         return clone;
142     }
143 });
144
145
146 return {
147     elementNode: {
148         methods: {
149             getMetadata: function() {
150                 if(!this.getData(metadataKey)) {
151                     this.setData(metadataKey, new Metadata(this));
152                 }
153                 return this.getData(metadataKey);
154             }
155         }
156     }
157 };
158
159 });
160