splitting text
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / documentElement.js
1 define([
2 'libs/jquery',
3 'libs/underscore',
4 'modules/documentCanvas/canvas/utils'
5 ], function($, _, utils) {
6     
7 'use strict';
8 /* global Node:false */
9
10 // DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
11 var DocumentElement = function(wlxmlNode, canvas) {
12     this.wlxmlNode = wlxmlNode;
13     this.canvas = canvas;
14     this.state = {
15         exposed: false,
16         active: false
17     };
18
19     this.dom = this.createDOM();
20     this.dom.data('canvas-element', this);
21     this.wlxmlNode.setData('canvasElement', this);
22 };
23
24 $.extend(DocumentElement.prototype, {
25     refreshPath: function() {
26         this.parents().forEach(function(parent) {
27             parent.refresh();
28         });
29         this.refresh();
30     },
31     refresh: function() {
32         // noop
33     },
34     updateState: function(toUpdate) {
35         var changes = {};
36         _.keys(toUpdate)
37             .filter(function(key) {
38                 return this.state.hasOwnProperty(key);
39             }.bind(this))
40             .forEach(function(key) {
41                 if(this.state !== toUpdate[key]) {
42                     this.state[key] = changes[key] = toUpdate[key];
43                 }
44             }.bind(this));
45         if(_.isFunction(this.onStateChange)) {
46             this.onStateChange(changes);
47             if(_.isBoolean(changes.active)) {
48                 if(changes.active) {
49                     var ptr = this;
50                     while(ptr && ptr.wlxmlNode.getTagName() === 'span') {
51                         ptr = ptr.parent();
52                     }
53                     if(ptr && ptr.gutterGroup) {
54                         ptr.gutterGroup.show();
55                     }
56                 }
57             }
58         }
59     },
60     parent: function() {
61         var parents = this.dom.parents('[document-node-element]');
62         if(parents.length) {
63             return this.canvas.getDocumentElement(parents[0]);
64         }
65         return null;
66     },
67
68     parents: function() {
69         var parents = [],
70             parent = this.parent();
71         while(parent) {
72             parents.push(parent);
73             parent = parent.parent();
74         }
75         return parents;
76     },
77
78     sameNode: function(other) {
79         return other && (typeof other === typeof this) && other.dom[0] === this.dom[0];
80     },
81     isRootElement: function() {
82         return this.sameNode(this.canvas.rootElement);
83     },
84
85     trigger: function() {
86         this.canvas.eventBus.trigger.apply(this.canvas.eventBus, Array.prototype.slice.call(arguments, 0));
87     }
88
89
90 });
91
92
93 // DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
94 var DocumentNodeElement = function(wlxmlNode, canvas) {
95     DocumentElement.call(this, wlxmlNode, canvas);
96     this.init(this.dom);
97 };
98
99
100 var manipulate = function(e, params, action) {
101     var element;
102     if(params instanceof DocumentElement) {
103         element = params;
104     } else {
105         element = e.canvas.createElement(params);
106     }
107     if(element.dom) {
108         e.dom[action](element.dom);
109         e.refreshPath();
110     }
111     return element;
112 };
113
114 DocumentNodeElement.prototype = Object.create(DocumentElement.prototype);
115
116
117 $.extend(DocumentNodeElement.prototype, {
118     defaultDisplayStyle: 'block',
119     init: function() {},
120     addWidget: function(widget) {
121         this.dom.children('.canvas-widgets').append(widget.DOM ? widget.DOM : widget);
122     },
123     clearWidgets: function() {
124         this.dom.children('.canvas-widgets').empty();
125     },
126     addToGutter: function(view) {
127         if(!this.gutterGroup) {
128             this.gutterGroup = this.canvas.gutter.createViewGroup({
129                 offsetHint: function() {
130                     return this.canvas.getElementOffset(this);
131                 }.bind(this)
132             }, this);
133         }
134         this.gutterGroup.addView(view);
135     },
136     handle: function(event) {
137         var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1);
138         if(this[method]) {
139             this[method](event);
140         }
141     },
142     createDOM: function() {
143         var wrapper = $('<div>').attr('document-node-element', ''),
144             widgetsContainer = $('<div>')
145                 .addClass('canvas-widgets'),
146             contentContainer = $('<div>')
147                 .attr('document-element-content', '');
148         
149         wrapper.append(contentContainer, widgetsContainer);
150         widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
151         return wrapper;
152     },
153     _container: function() {
154         return this.dom.children('[document-element-content]');
155     },
156     detach: function(isChild) {
157         var parents;
158
159         if(this.gutterGroup) {
160             this.gutterGroup.remove();
161         }
162         if(_.isFunction(this.children)) {
163             this.children().forEach(function(child) {
164                 child.detach(true);
165             });
166         }
167
168         if(!isChild) {
169             parents = this.parents();
170             this.dom.detach();
171             if(parents[0]) {
172                 parents[0].refreshPath();
173             }
174         }
175         return this;
176     },
177     before: function(params) {
178         return manipulate(this, params, 'before');
179
180     },
181     after: function(params) {
182         return manipulate(this, params, 'after');
183     },
184
185     isBlock: function() {
186         return this.dom.css('display') === 'block';
187     },
188
189     displayAsBlock: function() {
190         this.dom.css('display', 'block');
191         this._container().css('display', 'block');
192     },
193     displayInline: function() {
194         this.dom.css('display', 'inline');
195         this._container().css('display', 'inline');
196     },
197     displayAs: function(what) {
198         // [this.dom(), this._container()].forEach(e) {
199         //     var isBlock = window.getComputedStyle(e).display === 'block';
200         //     if(!isBlock && what === 'block') {
201         //         e.css('display', what);
202         //     } else if(isBlock && what === 'inline') {
203         //         e.css('display')
204         //     }
205         // })
206         this.dom.css('display', what);
207         this._container().css('display', what);
208     }
209 });
210
211
212 // DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
213 var DocumentTextElement = function(wlxmlTextNode, canvas) {
214     DocumentElement.call(this, wlxmlTextNode, canvas);
215 };
216
217 $.extend(DocumentTextElement, {
218     isContentContainer: function(htmlElement) {
219         return htmlElement.nodeType === Node.TEXT_NODE && $(htmlElement).parent().is('[document-text-element]');
220     }
221 });
222
223 DocumentTextElement.prototype = Object.create(DocumentElement.prototype);
224
225 $.extend(DocumentTextElement.prototype, {
226     createDOM: function() {
227         var dom = $('<div>')
228             .attr('document-text-element', '')
229             .text(this.wlxmlNode.getText() || utils.unicode.ZWS);
230         return dom;
231     },
232     detach: function(isChild) {
233         if(!isChild) {
234             this.dom.detach();
235         }
236         return this;
237     },
238     setText: function(text) {
239         if(text === '') {
240             text = utils.unicode.ZWS;
241         }
242         if(text !== this.getText()) {
243             this.dom.contents()[0].data = text;
244         }
245     },
246     handle: function(event) {
247         this.setText(event.meta.node.getText());
248     },
249     getText: function(options) {
250         options = _.extend({raw: false}, options || {});
251         var toret = this.dom.text();
252         if(!options.raw) {
253             toret = toret.replace(utils.unicode.ZWS, '');
254         }
255         return toret;
256     },
257     isEmpty: function() {
258         // Having at least Zero Width Space is guaranteed be Content Observer
259         return this.dom.contents()[0].data === utils.unicode.ZWS;
260     },
261     after: function(params) {
262         if(params instanceof DocumentTextElement || params.text) {
263             return false;
264         }
265         var element;
266         if(params instanceof DocumentNodeElement) {
267             element = params;
268         } else {
269             element = this.canvas.createElement(params);
270         }
271         if(element.dom) {
272             this.dom.wrap('<div>');
273             this.dom.parent().after(element.dom);
274             this.dom.unwrap();
275             this.refreshPath();
276         }
277         return element;
278     },
279     before: function(params) {
280         if(params instanceof DocumentTextElement || params.text) {
281             return false;
282         }
283         var element;
284         if(params instanceof DocumentNodeElement) {
285             element = params;
286         } else {
287             element = this.canvas.createElement(params);
288         }
289         if(element.dom) {
290             this.dom.wrap('<div>');
291             this.dom.parent().before(element.dom);
292             this.dom.unwrap();
293             this.refreshPath();
294         }
295         return element;
296     },
297
298     children: function() {
299         return [];
300     }
301
302 });
303
304
305 return {
306     DocumentElement: DocumentElement,
307     DocumentNodeElement: DocumentNodeElement,
308     DocumentTextElement: DocumentTextElement
309 };
310
311 });