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