5e653b8d13665fd526d51681c7613c89a7eed948
[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
82     trigger: function() {
83         this.canvas.eventBus.trigger.apply(this.canvas.eventBus, Array.prototype.slice.call(arguments, 0));
84     }
85
86
87 });
88
89
90 // DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
91 var DocumentNodeElement = function(wlxmlNode, canvas) {
92     DocumentElement.call(this, wlxmlNode, canvas);
93     this.init(this.dom);
94 };
95
96
97 var manipulate = function(e, params, action) {
98     var element;
99     if(params instanceof DocumentElement) {
100         element = params;
101     } else {
102         element = e.canvas.createElement(params);
103     }
104     if(element.dom) {
105         e.dom[action](element.dom);
106         e.refreshPath();
107     }
108     return element;
109 };
110
111 DocumentNodeElement.prototype = Object.create(DocumentElement.prototype);
112
113
114 $.extend(DocumentNodeElement.prototype, {
115     defaultDisplayStyle: 'block',
116     init: function() {},
117     addWidget: function(widget) {
118         this.dom.children('.canvas-widgets').append(widget.DOM ? widget.DOM : widget);
119     },
120     clearWidgets: function() {
121         this.dom.children('.canvas-widgets').empty();
122     },
123     addToGutter: function(view) {
124         if(!this.gutterGroup) {
125             this.gutterGroup = this.canvas.gutter.createViewGroup({
126                 offsetHint: function() {
127                     return this.canvas.getElementOffset(this);
128                 }.bind(this)
129             }, this);
130         }
131         this.gutterGroup.addView(view);
132     },
133     handle: function(event) {
134         var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1);
135         if(this[method]) {
136             this[method](event);
137         }
138     },
139     createDOM: function() {
140         var wrapper = $('<div>').attr('document-node-element', ''),
141             widgetsContainer = $('<div>')
142                 .addClass('canvas-widgets'),
143             contentContainer = $('<div>')
144                 .attr('document-element-content', '');
145         
146         wrapper.append(contentContainer, widgetsContainer);
147         widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
148         return wrapper;
149     },
150     _container: function() {
151         return this.dom.children('[document-element-content]');
152     },
153     detach: function(isChild) {
154         var parents;
155
156         if(this.gutterGroup) {
157             this.gutterGroup.remove();
158         }
159         if(_.isFunction(this.children)) {
160             this.children().forEach(function(child) {
161                 child.detach(true);
162             });
163         }
164
165         if(!isChild) {
166             parents = this.parents();
167             this.dom.detach();
168             if(parents[0]) {
169                 parents[0].refreshPath();
170             }
171         }
172         return this;
173     },
174     before: function(params) {
175         return manipulate(this, params, 'before');
176
177     },
178     after: function(params) {
179         return manipulate(this, params, 'after');
180     },
181
182     isBlock: function() {
183         return this.dom.css('display') === 'block';
184     },
185
186     displayAsBlock: function() {
187         this.dom.css('display', 'block');
188         this._container().css('display', 'block');
189     },
190     displayInline: function() {
191         this.dom.css('display', 'inline');
192         this._container().css('display', 'inline');
193     },
194     displayAs: function(what) {
195         // [this.dom(), this._container()].forEach(e) {
196         //     var isBlock = window.getComputedStyle(e).display === 'block';
197         //     if(!isBlock && what === 'block') {
198         //         e.css('display', what);
199         //     } else if(isBlock && what === 'inline') {
200         //         e.css('display')
201         //     }
202         // })
203         this.dom.css('display', what);
204         this._container().css('display', what);
205     }
206 });
207
208
209 // DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
210 var DocumentTextElement = function(wlxmlTextNode, canvas) {
211     DocumentElement.call(this, wlxmlTextNode, canvas);
212 };
213
214 $.extend(DocumentTextElement, {
215     isContentContainer: function(htmlElement) {
216         return htmlElement.nodeType === Node.TEXT_NODE && $(htmlElement).parent().is('[document-text-element]');
217     }
218 });
219
220 DocumentTextElement.prototype = Object.create(DocumentElement.prototype);
221
222 $.extend(DocumentTextElement.prototype, {
223     createDOM: function() {
224         var dom = $('<div>')
225             .attr('document-text-element', '')
226             .text(this.wlxmlNode.getText() || utils.unicode.ZWS);
227         return dom;
228     },
229     detach: function(isChild) {
230         if(!isChild) {
231             this.dom.detach();
232         }
233         return this;
234     },
235     setText: function(text) {
236         if(text === '') {
237             text = utils.unicode.ZWS;
238         }
239         if(text !== this.getText()) {
240             this.dom.contents()[0].data = text;
241         }
242     },
243     handle: function(event) {
244         this.setText(event.meta.node.getText());
245     },
246     getText: function(options) {
247         options = _.extend({raw: false}, options || {});
248         var toret = this.dom.text();
249         if(!options.raw) {
250             toret = toret.replace(utils.unicode.ZWS, '');
251         }
252         return toret;
253     },
254     isEmpty: function() {
255         // Having at least Zero Width Space is guaranteed be Content Observer
256         return this.dom.contents()[0].data === utils.unicode.ZWS;
257     },
258     after: function(params) {
259         if(params instanceof DocumentTextElement || params.text) {
260             return false;
261         }
262         var element;
263         if(params instanceof DocumentNodeElement) {
264             element = params;
265         } else {
266             element = this.canvas.createElement(params);
267         }
268         if(element.dom) {
269             this.dom.wrap('<div>');
270             this.dom.parent().after(element.dom);
271             this.dom.unwrap();
272             this.refreshPath();
273         }
274         return element;
275     },
276     before: function(params) {
277         if(params instanceof DocumentTextElement || params.text) {
278             return false;
279         }
280         var element;
281         if(params instanceof DocumentNodeElement) {
282             element = params;
283         } else {
284             element = this.canvas.createElement(params);
285         }
286         if(element.dom) {
287             this.dom.wrap('<div>');
288             this.dom.parent().before(element.dom);
289             this.dom.unwrap();
290             this.refreshPath();
291         }
292         return element;
293     },
294
295     children: function() {
296         return [];
297     }
298
299 });
300
301
302 return {
303     DocumentElement: DocumentElement,
304     DocumentNodeElement: DocumentNodeElement,
305     DocumentTextElement: DocumentTextElement
306 };
307
308 });