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