integration wip: marking element as current
[fnpeditor.git] / modules / documentCanvas / canvas / documentElement.js
1 define([
2 'libs/jquery-1.9.1.min',
3 'libs/underscore-min'
4 ], function($, _) {
5     
6 'use strict';
7
8
9 // DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
10 var DocumentElement = function(htmlElement, canvas) {
11     if(arguments.length === 0)
12         return;
13     this.canvas = canvas;
14     this._setupDOMHandler(htmlElement);
15 }
16
17 var elementTypeFromParams = function(params) {
18     return params.text !== undefined ? DocumentTextElement : DocumentNodeElement;
19
20 };
21
22 $.extend(DocumentElement, {
23     create: function(params, canvas) {
24         return elementTypeFromParams(params).create(params);
25     },
26
27     createDOM: function(params) {
28         return elementTypeFromParams(params).createDOM(params);
29     },
30
31     fromHTMLElement: function(htmlElement, canvas) {
32         var $element = $(htmlElement);
33         if(htmlElement.nodeType === Node.ELEMENT_NODE && $element.attr('wlxml-tag'))
34             return DocumentNodeElement.fromHTMLElement(htmlElement, canvas);
35         if($element.attr('wlxml-text') !== undefined || (htmlElement.nodeType === Node.TEXT_NODE && $element.parent().attr('wlxml-text') !== undefined))
36             return DocumentTextElement.fromHTMLElement(htmlElement, canvas);
37         return undefined;
38     }
39 });
40
41 $.extend(DocumentElement.prototype, {
42     _setupDOMHandler: function(htmlElement) {
43         this.$element = $(htmlElement);
44     },
45     dom: function() {
46         return this.$element;
47     },
48     parent: function() {
49         var parents = this.$element.parents('[wlxml-tag]');
50         if(parents.length)
51             return DocumentElement.fromHTMLElement(parents[0], this.canvas);
52         return null;
53     },
54
55     sameNode: function(other) {
56         return other && (typeof other === typeof this) && other.dom()[0] === this.dom()[0];
57     },
58
59     wrapWithNodeElement: function(wlxmlNode) {
60         var wrapper = DocumentNodeElement.create({tag: wlxmlNode.tag, klass: wlxmlNode.klass});
61         this.dom().replaceWith(wrapper.dom());
62         wrapper.append(this);
63         return wrapper;
64     },
65
66     detach: function() {
67         this.dom().detach();
68         this.canvas = null;
69     },
70
71     markAsCurrent: function() {
72         this.canvas.markAsCurrent(this);
73     }
74 });
75
76
77 // DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
78 var DocumentNodeElement = function(htmlElement, canvas) {
79     DocumentElement.call(this, htmlElement, canvas);
80 };
81
82 $.extend(DocumentNodeElement, {
83     createDOM: function(params) {
84         var dom = $('<div>')
85             .attr('wlxml-tag', params.tag);
86         if(params.klass)
87             dom.attr('wlxml-class', params.klass.replace(/\./g, '-'));
88         return dom;
89     },
90
91     create: function(params, canvas) {
92         return this.fromHTMLElement(this.createDOM(params)[0]);
93     },
94
95     fromHTMLElement: function(htmlElement, canvas) {
96         return new this(htmlElement, canvas);
97     }
98 });
99
100 var manipulate = function(e, params, action) {
101     var element;
102     if(params instanceof DocumentElement) {
103         element = params;
104     } else {
105         element = DocumentElement.create(params);
106     }
107     e.dom()[action](element.dom());
108     return element;
109 };
110
111 DocumentNodeElement.prototype = new DocumentElement();
112
113 $.extend(DocumentNodeElement.prototype, {
114     append: function(params) {
115         manipulate(this, params, 'append');
116     },
117     before: function(params) {
118         manipulate(this, params, 'before');
119
120     },
121     after: function(params) {
122         manipulate(this, params, 'after');
123     },
124     children: function() {
125         var toret = [];
126         if(this instanceof DocumentTextElement)
127             return toret;
128
129
130         var elementContent = this.dom().contents();
131         var element = this;
132         elementContent.each(function(idx) {
133             var childElement = DocumentElement.fromHTMLElement(this, element.canvas);
134             if(idx === 0 && elementContent.length > 1 && elementContent[1].nodeType === Node.ELEMENT_NODE && (childElement instanceof DocumentTextElement) && $.trim($(this).text()) === '')
135                 return true;
136             if(idx > 0 && childElement instanceof DocumentTextElement) {
137                 if(toret[toret.length-1] instanceof DocumentNodeElement && $.trim($(this).text()) === '')
138                     return true;
139             }
140             toret.push(childElement);
141         });
142         return toret;
143     },
144     childIndex: function(child) {
145         var children = this.children(),
146             toret = null;
147         children.forEach(function(c, idx) {
148             if(c.sameNode(child)) {
149                 toret = idx;
150                 return false;
151             }
152         });
153         return toret;
154     },
155     getWlxmlTag: function() {
156         return this.dom().attr('wlxml-tag');
157     },
158     setWlxmlTag: function(tag) {
159         this.dom().attr('wlxml-tag', tag);
160     },
161     getWlxmlClass: function() {
162         var klass = this.dom().attr('wlxml-class');
163         if(klass)
164             return klass.replace('-', '.');
165         return undefined;
166     },
167     setWlxmlClass: function(klass) {
168         if(klass)
169             this.dom().attr('wlxml-class', klass);
170         else
171             this.dom().removeAttr('wlxml-class');
172     },
173     is: function(what) {
174         if(what === 'list' && _.contains(['list-items', 'list-items-enum'], this.dom().attr('wlxml-class')))
175             return true;
176         return false;
177     }
178 });
179
180
181 // DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
182 var DocumentTextElement = function(htmlElement, canvas) {
183     DocumentElement.call(this, htmlElement, canvas);
184 };
185
186 $.extend(DocumentTextElement, {
187     createDOM: function(params) {
188         return $('<div>')
189             .attr('wlxml-text', '')
190             .text(params.text);
191     },
192
193     create: function(params, canvas) {
194         return this.fromHTMLElement(this.createDOM(params)[0]);
195     },
196
197     fromHTMLElement: function(htmlElement, canvas) {
198         return new this(htmlElement, canvas);
199     }
200 });
201
202 DocumentTextElement.prototype = new DocumentElement();
203
204 $.extend(DocumentTextElement.prototype, {
205     _setupDOMHandler: function(htmlElement) {
206         var $element = $(htmlElement);
207         if(htmlElement.nodeType === Node.TEXT_NODE)
208             this.$element = $element.parent();
209         else
210             this.$element = $element;
211     },
212     setText: function(text) {
213         this.dom().contents()[0].data = text;
214     },
215     getText: function() {
216         return this.dom().text();
217     },
218     after: function(params) {
219         if(params instanceof DocumentTextElement || params.text)
220             return false;
221         var element;
222         if(params instanceof DocumentNodeElement) {
223             element = params;
224         } else {
225             element = DocumentNodeElement.create(params);
226         }
227         this.dom().wrap('<div>');
228         this.dom().parent().after(element.dom());
229         this.dom().unwrap();
230         return element;
231     },
232     before: function(params) {
233         if(params instanceof DocumentTextElement || params.text)
234             return false;
235         var element;
236         if(params instanceof DocumentNodeElement) {
237             element = params;
238         } else {
239             element = DocumentNodeElement.create(params);
240         }
241         this.dom().wrap('<div>');
242         this.dom().parent().before(element.dom());
243         this.dom().unwrap();
244         return element;
245     },
246     wrapWithNodeElement: function(wlxmlNode) {
247         if(typeof wlxmlNode.start === 'number' && typeof wlxmlNode.end === 'number') {
248             return this.canvas.wrapText({
249                 inside: this.parent(),
250                 textNodeIdx: this.parent().childIndex(this),
251                 offsetStart: Math.min(wlxmlNode.start, wlxmlNode.end),
252                 offsetEnd: Math.max(wlxmlNode.start, wlxmlNode.end),
253                 _with: {tag: wlxmlNode.tag, klass: wlxmlNode.klass}
254             });
255         } else {
256             return DocumentElement.prototype.wrapWithNodeElement.call(this, wlxmlNode);
257         }
258     },
259     unwrap: function() {
260         var parent = this.parent();
261         if(parent.children().length === 1) {
262             var grandParent = parent.parent();
263             if(grandParent) {
264                 var grandParentChildren = grandParent.children(),
265                     idx = grandParent.childIndex(parent),
266                     prev = idx - 1 > -1 ? grandParentChildren[idx-1] : null,
267                     next = idx + 1 < grandParentChildren.length ? grandParentChildren[idx+1] : null;
268                 if(prev && next) {
269                     prev.setText(prev.getText() + this.getText() + next.getText());
270                     next.detach();
271                 } else if (prev || next) {
272                     var target = prev ? prev : next;
273                     target.setText(target.getText() + this.getText());
274                 } else {
275                     parent.after(this);
276                 }
277             } else {
278                 parent.after(this);
279             }
280             parent.detach();
281         }
282     },
283     split: function(params) {
284         var parentElement = this.parent(),
285             myIdx = parentElement.childIndex(this),
286             myCanvas = this.canvas,
287             passed = false,
288             succeedingChildren = [],
289             thisElement = this,
290             prefix = this.getText().substr(0, params.offset),
291             suffix = this.getText().substr(params.offset);
292
293         parentElement.children().forEach(function(child) {
294             if(passed)
295                 succeedingChildren.push(child);
296             if(child.sameNode(thisElement))
297                 passed = true;
298         });
299
300         if(prefix.length > 0)
301             this.setText(prefix);
302         else
303             this.detach();
304         
305         var newElement = DocumentNodeElement.create({tag: parentElement.getWlxmlTag(), klass: parentElement.getWlxmlClass()}, myCanvas);
306         parentElement.after(newElement);
307
308         if(suffix.length > 0)
309             newElement.append({text: suffix});
310         succeedingChildren.forEach(function(child) {
311             newElement.append(child);
312         });
313     },
314 });
315
316 return {
317     DocumentElement: DocumentElement,
318     DocumentNodeElement: DocumentNodeElement,
319     DocumentTextElement: DocumentTextElement
320 };
321
322 });