smartxml: refactor wrapping transformation arguments with path accessors
[fnpeditor.git] / src / smartxml / transformations.js
1 define(function(require) {
2     
3 'use strict';
4
5 var _ = require('libs/underscore'),
6     toret = {};
7
8 var getTransDesc = function(desc) {
9     if(typeof desc === 'function') {
10         desc = {impl: desc};
11     }
12     if(!desc.impl) {
13         throw new Error('Got transformation description without implementation.');
14     }
15     return desc;
16 };
17
18 toret.createGenericTransformation = function(desc, name) {
19     desc = getTransDesc(desc);
20     
21     var GenericTransformation = function(document, args) {
22         this.args = args || [];
23
24         var transformation = this;
25         // _.keys(this.args).forEach(function(key) {
26         //     if(transformation.args[key].nodeType) { //@@ change to instanceof check, fix circular dependency
27         //         var value = transformation.args[key],
28         //             path = value.getPath();
29         //         Object.defineProperty(transformation.args, key, {
30         //             get: function() {
31         //                 if(transformation.hasRun) {
32         //                     //console.log('returning via path');
33         //                     return transformation.document.getNodeByPath(path);
34         //                 } else {
35         //                     //console.log('returning original arg');
36         //                     return value;
37
38         //                 }
39         //             }
40         //         });
41         //     }
42         // });
43
44         // potem spr na dotychczasowych undo/redo tests;
45         
46         var patchObject = function(obj, depth) {
47             depth = _.isNumber(depth) ? depth : 1;
48             if(depth > 3) {
49                 return;
50             }
51             _.keys(obj).forEach(function(key) {
52                 var value = obj[key];
53                 if(value) {
54                     if(value.nodeType) {
55                         transformation.wrapNodeProperty(obj, key);
56                     } else if(_.isObject(value)) {
57                         patchObject(value, depth+1);
58                     }
59                 }
60             });
61         };
62
63         this.args.forEach(function(arg, idx, args) {
64             if(arg) {
65                 if(arg.nodeType) { // ~
66                     transformation.wrapNodeProperty(args, idx);
67                 } else if(_.isObject(arg)) {
68                     patchObject(arg);
69                 }
70             }
71         });
72
73         this.document = document;
74         this.hasRun = false;
75         if(desc.init) {
76             desc.init.call(this);
77         }
78     };
79     _.extend(GenericTransformation.prototype, {
80         name: name,
81         run: function(options) {
82             var changeRoot;
83             if(!desc.undo && options.beUndoable) {
84                 changeRoot = this.getChangeRoot();
85                 if(!changeRoot) {
86                      throw new Error(
87                          'Transformation {name} returned invalid change root value'
88                          .replace('{name}', name)
89                      );
90                 }
91                 this.changeRootPath = changeRoot.getPath();
92                 this.snapshot = changeRoot.clone();
93             }
94             var argsToPass = desc.undo ? [this].concat(this.args) : this.args;
95             var toret = desc.impl.apply(this.context, argsToPass);
96             this.hasRun = true;
97             return toret;
98         },
99         undo: function() {
100             if(desc.undo) {
101                 desc.undo.call(this.context, this);
102             } else {
103                 this.document.getNodeByPath(this.changeRootPath).replaceWith(this.snapshot);
104             }
105         },
106         getChangeRoot: desc.getChangeRoot || function() {
107             return this.document.root;
108         },
109         wrapNodeProperty: function(object, propName, value) {
110             var transformation = this,
111                 path;
112             
113             value = value || object[propName];
114             if(value && value.nodeType) {
115                 path = value.getPath();
116                 Object.defineProperty(object, propName, {
117                     get: function() {
118                         if(transformation.hasRun && path) {
119                             return transformation.document.getNodeByPath(path);
120                         } else {
121                             return value;
122                         }
123                     }
124                 });
125             }
126         }
127     });
128
129     return GenericTransformation;
130 };
131
132 toret.createContextTransformation = function(desc, name) {
133     var GenericTransformation = toret.createGenericTransformation(desc, name);
134
135     var ContextTransformation = function(document, object, args) {
136         GenericTransformation.call(this, document, args);
137
138         if(document === object) {
139             this.context = document;
140         } else {
141             this.wrapNodeProperty(this, 'context', object);
142         }
143     };
144     ContextTransformation.prototype = Object.create(GenericTransformation.prototype);
145     return ContextTransformation;
146 };
147
148 return toret;
149
150 });