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