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