5a40f30f8a1089f618aa7f9b27e8a28f437c3504
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / documentElement.js
1 define([
2 'libs/jquery',
3 'libs/underscore',
4 'modules/documentCanvas/canvas/utils',
5 'modules/documentCanvas/canvas/wlxmlManagers'
6 ], function($, _, utils, wlxmlManagers) {
7     
8 'use strict';
9 /* global Node:false, document:false */
10
11
12 // DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
13 var DocumentElement = function(wlxmlNode, canvas) {
14     if(arguments.length === 0) {
15         return;
16     }
17     this.wlxmlNode = wlxmlNode;
18     this.canvas = canvas;
19
20     this.$element = this.createDOM();
21     this.$element.data('canvas-element', this);
22 };
23
24 $.extend(DocumentElement.prototype, {
25     bound: function() {
26         return $.contains(document.documentElement, this.dom()[0]);
27     },
28     dom: function() {
29         return this.$element;
30     },
31     parent: function() {
32         var parents = this.$element.parents('[document-node-element]');
33         if(parents.length) {
34             return this.canvas.getDocumentElement(parents[0]);
35         }
36         return null;
37     },
38
39     parents: function() {
40         var parents = [],
41             parent = this.parent();
42         while(parent) {
43             parents.push(parent);
44             parent = parent.parent();
45         }
46         return parents;
47     },
48
49     sameNode: function(other) {
50         return other && (typeof other === typeof this) && other.dom()[0] === this.dom()[0];
51     },
52
53     markAsCurrent: function() {
54         this.canvas.markAsCurrent(this);
55     },
56
57     getVerticallyFirstTextElement: function() {
58         var toret;
59         this.children().some(function(child) {
60             if(child instanceof DocumentTextElement) {
61                 toret = child;
62                 return true; // break
63             } else {
64                 toret = child.getVerticallyFirstTextElement();
65                 if(toret) {
66                     return true; // break
67                 }
68             }
69         });
70         return toret;
71     },
72
73     getPreviousTextElement: function(includeInvisible) {
74         return this.getNearestTextElement('above', includeInvisible);
75     },
76
77     getNextTextElement: function(includeInvisible) {
78         return this.getNearestTextElement('below', includeInvisible);
79     },
80
81     getNearestTextElement: function(direction, includeInvisible) {
82         includeInvisible = includeInvisible !== undefined ? includeInvisible : false;
83         var selector = '[document-text-element]' + (includeInvisible ? '' : ':visible');
84         return this.canvas.getDocumentElement(utils.nearestInDocumentOrder(selector, direction, this.dom()[0]));
85     },
86
87     exec: function(method) {
88         if(this.manager && this.manager[method]) {
89             return this.manager[method].apply(this.manager, Array.prototype.slice.call(arguments, 1));
90         }
91     }
92 });
93
94
95 // DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
96 var DocumentNodeElement = function(wlxmlNode, canvas) {
97     DocumentElement.call(this, wlxmlNode, canvas);
98     wlxmlNode.setData('canvasElement', this);
99 };
100
101
102 var manipulate = function(e, params, action) {
103     var element;
104     if(params instanceof DocumentElement) {
105         element = params;
106     } else {
107         element = e.canvas.createElement(params);
108     }
109     var target = (action === 'append' || action === 'prepend') ? e._container() : e.dom();
110     target[action](element.dom());
111     return element;
112 };
113
114 DocumentNodeElement.prototype = new DocumentElement();
115
116
117 $.extend(DocumentNodeElement.prototype, {
118     createDOM: function() {
119         var dom = $('<div>')
120                 .attr('document-node-element', ''),
121             widgetsContainer = $('<div>')
122                 .addClass('canvas-widgets')
123                 .attr('contenteditable', false),
124             container = $('<div>')
125                 .attr('document-element-content', '');
126         
127         dom.append(widgetsContainer, container);
128         // Make sure widgets aren't navigable with arrow keys
129         widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
130         this.$element = dom; //@!!!
131
132         this.setWlxmlTag(this.wlxmlNode.getTagName());
133         this.setWlxmlClass(this.wlxmlNode.getClass());
134
135         this.wlxmlNode.contents().forEach(function(node) {
136             container.append(this.canvas.createElement(node).dom());
137         }.bind(this));
138         return dom;
139     },
140     _container: function() {
141         return this.dom().children('[document-element-content]');
142     },
143     detach: function() {
144         this.dom().detach();
145         this.canvas = null;
146         return this;
147     },
148     append: function(params) {
149         return manipulate(this, params, 'append');
150     },
151     prepend: function(params) {
152         return manipulate(this, params, 'prepend');
153     },
154     before: function(params) {
155         return manipulate(this, params, 'before');
156
157     },
158     after: function(params) {
159         return manipulate(this, params, 'after');
160     },
161     children: function() {
162         var toret = [];
163         if(this instanceof DocumentTextElement) {
164             return toret;
165         }
166
167
168         var elementContent = this._container().contents();
169         var element = this;
170         elementContent.each(function() {
171             var childElement = element.canvas.getDocumentElement(this);
172             if(childElement === undefined) {
173                 return true;
174             }
175             toret.push(childElement);
176         });
177         return toret;
178     },
179     childIndex: function(child) {
180         var children = this.children(),
181             toret = null;
182         children.forEach(function(c, idx) {
183             if(c.sameNode(child)) {
184                 toret = idx;
185                 return false;
186             }
187         });
188         return toret;
189     },
190     getWlxmlTag: function() {
191         return this._container().attr('wlxml-tag');
192     },
193     setWlxmlTag: function(tag) {
194         this._container().attr('wlxml-tag', tag);
195     },
196     getWlxmlClass: function() {
197         var klass = this._container().attr('wlxml-class');
198         if(klass) {
199             return klass.replace(/-/g, '.');
200         }
201         return undefined;
202     },
203     setWlxmlClass: function(klass) {
204         if(klass === this.getWlxmlClass()) {
205             return;
206         }
207         if(klass) {
208             this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
209         }
210         else {
211             this._container().removeAttr('wlxml-class');
212         }
213         this.manager = wlxmlManagers.getFor(this);
214         this.manager.setup();
215     },
216     toggleLabel: function(toggle) {
217         var displayCss = toggle ? 'inline-block' : 'none';
218         var label = this.dom().children('.canvas-widgets').find('.canvas-widget-label');
219         label.css('display', displayCss);
220         this.toggleHighlight(toggle);
221     },
222
223     toggleHighlight: function(toggle) {
224         this._container().toggleClass('highlighted-element', toggle);
225     },
226
227     toggle: function(toggle) {
228         if(this.manager) {
229             this.manager.toggle(toggle);
230         }
231     }
232 });
233
234
235 // DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
236 var DocumentTextElement = function(wlxmlTextNode, canvas) {
237     DocumentElement.call(this, wlxmlTextNode, canvas);
238 };
239
240 $.extend(DocumentTextElement, {
241     isContentContainer: function(htmlElement) {
242         return htmlElement.nodeType === Node.TEXT_NODE && $(htmlElement).parent().is('[document-text-element]');
243     }
244 });
245
246 DocumentTextElement.prototype = new DocumentElement();
247
248 $.extend(DocumentTextElement.prototype, {
249     createDOM: function() {
250         return $('<div>')
251             .attr('document-text-element', '')
252             .text(this.wlxmlNode.getText() || utils.unicode.ZWS);
253     },
254     detach: function() {
255         this.dom().detach();
256         this.canvas = null;
257         return this;
258     },
259     setText: function(text) {
260         this.dom().contents()[0].data = text;
261     },
262     getText: function(options) {
263         options = _.extend({raw: false}, options || {});
264         var toret = this.dom().text();
265         if(!options.raw) {
266             toret = toret.replace(utils.unicode.ZWS, '');
267         }
268         return toret;
269     },
270     isEmpty: function() {
271         // Having at least Zero Width Space is guaranteed be Content Observer
272         return this.dom().contents()[0].data === utils.unicode.ZWS;
273     },
274     after: function(params) {
275         if(params instanceof DocumentTextElement || params.text) {
276             return false;
277         }
278         var element;
279         if(params instanceof DocumentNodeElement) {
280             element = params;
281         } else {
282             element = this.canvas.createElement(params);
283         }
284         this.dom().wrap('<div>');
285         this.dom().parent().after(element.dom());
286         this.dom().unwrap();
287         return element;
288     },
289     before: function(params) {
290         if(params instanceof DocumentTextElement || params.text) {
291             return false;
292         }
293         var element;
294         if(params instanceof DocumentNodeElement) {
295             element = params;
296         } else {
297             element = this.canvas.createElement(params);
298         }
299         this.dom().wrap('<div>');
300         this.dom().parent().before(element.dom());
301         this.dom().unwrap();
302         return element;
303     },
304
305     toggleHighlight: function() {
306         // do nothing for now
307     }
308 });
309
310 return {
311     DocumentElement: DocumentElement,
312     DocumentNodeElement: DocumentNodeElement,
313     DocumentTextElement: DocumentTextElement
314 };
315
316 });