Wrapping text node containing DocumentTextElement's text
[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
72
73 // DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
74 var DocumentNodeElement = function(htmlElement, canvas) {
75     DocumentElement.call(this, htmlElement, canvas);
76 };
77
78 $.extend(DocumentNodeElement, {
79     createDOM: function(params) {
80         var dom = $('<div>').attr('wlxml-tag', params.tag);
81         if(params.klass)
82             dom.attr('wlxml-class', params.klass);
83         return dom;
84     },
85
86     create: function(params, canvas) {
87         return this.fromHTMLElement(this.createDOM(params)[0]);
88     },
89
90     fromHTMLElement: function(htmlElement, canvas) {
91         return new this(htmlElement, canvas);
92     }
93 });
94
95 var manipulate = function(e, params, action) {
96     var element;
97     if(params instanceof DocumentElement) {
98         element = params;
99     } else {
100         element = DocumentElement.create(params);
101     }
102     e.dom()[action](element.dom());
103     return element;
104 };
105
106 DocumentNodeElement.prototype = new DocumentElement();
107
108 $.extend(DocumentNodeElement.prototype, {
109     append: function(params) {
110         manipulate(this, params, 'append');
111     },
112     before: function(params) {
113         manipulate(this, params, 'before');
114
115     },
116     after: function(params) {
117         manipulate(this, params, 'after');
118     },
119     children: function() {
120         var toret = [];
121         if(this instanceof DocumentTextElement)
122             return toret;
123
124
125         var elementContent = this.dom().contents();
126         var element = this;
127         elementContent.each(function(idx) {
128             var childElement = DocumentElement.fromHTMLElement(this, element.canvas);
129             if(idx === 0 && elementContent.length > 1 && elementContent[1].nodeType === Node.ELEMENT_NODE && (childElement instanceof DocumentTextElement) && $.trim($(this).text()) === '')
130                 return true;
131             if(idx > 0 && childElement instanceof DocumentTextElement) {
132                 if(toret[toret.length-1] instanceof DocumentNodeElement && $.trim($(this).text()) === '')
133                     return true;
134             }
135             toret.push(childElement);
136         });
137         return toret;
138     },
139     childIndex: function(child) {
140         var children = this.children(),
141             toret = null;
142         children.forEach(function(c, idx) {
143             if(c.sameNode(child)) {
144                 toret = idx;
145                 return false;
146             }
147         });
148         return toret;
149     },
150     getWlxmlTag: function() {
151         return this.dom().attr('wlxml-tag');
152     },
153     setWlxmlTag: function(tag) {
154         this.dom().attr('wlxml-tag', tag);
155     },
156     getWlxmlClass: function() {
157         var klass = this.dom().attr('wlxml-class');
158         if(klass)
159             return klass.replace('-', '.');
160         return undefined;
161     },
162     setWlxmlClass: function(klass) {
163         if(klass)
164             this.dom().attr('wlxml-class', klass);
165         else
166             this.dom().removeAttr('wlxml-class');
167     },
168     is: function(what) {
169         if(what === 'list' && _.contains(['list-items', 'list-items-enum'], this.dom().attr('wlxml-class')))
170             return true;
171         return false;
172     }
173 });
174
175
176 // DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
177 var DocumentTextElement = function(htmlElement, canvas) {
178     DocumentElement.call(this, htmlElement, canvas);
179 };
180
181 $.extend(DocumentTextElement, {
182     createDOM: function(params) {
183         return $('<div>')
184             .attr('wlxml-text', '')
185             .text(params.text);
186     },
187
188     create: function(params, canvas) {
189         return this.fromHTMLElement(this.createDOM(params)[0]);
190     },
191
192     fromHTMLElement: function(htmlElement, canvas) {
193         return new this(htmlElement, canvas);
194     }
195 });
196
197 DocumentTextElement.prototype = new DocumentElement();
198
199 $.extend(DocumentTextElement.prototype, {
200     _setupDOMHandler: function(htmlElement) {
201         var $element = $(htmlElement);
202         if(htmlElement.nodeType === Node.TEXT_NODE)
203             this.$element = $element.parent();
204         else
205             this.$element = $element;
206     },
207     setText: function(text) {
208         this.dom().contents()[0].data = text;
209     },
210     getText: function() {
211         return this.dom().text();
212     },
213     after: function(params) {
214         if(params instanceof DocumentTextElement || params.text)
215             return false;
216         var element;
217         if(params instanceof DocumentNodeElement) {
218             element = params;
219         } else {
220             element = DocumentNodeElement.create(params);
221         }
222         this.dom().wrap('<div>');
223         this.dom().parent().after(element.dom());
224         this.dom().unwrap();
225         return element;
226     },
227     before: function(params) {
228         if(params instanceof DocumentTextElement || params.text)
229             return false;
230         var element;
231         if(params instanceof DocumentNodeElement) {
232             element = params;
233         } else {
234             element = DocumentNodeElement.create(params);
235         }
236         this.dom().wrap('<div>');
237         this.dom().parent().before(element.dom());
238         this.dom().unwrap();
239         return element;
240     },
241     wrapWithNodeElement: function(wlxmlNode) {
242         if(typeof wlxmlNode.start === 'number' && typeof wlxmlNode.end === 'number') {
243             return this.canvas.wrapText({
244                 inside: this.parent(),
245                 textNodeIdx: this.parent().childIndex(this),
246                 offsetStart: Math.min(wlxmlNode.start, wlxmlNode.end),
247                 offsetEnd: Math.max(wlxmlNode.start, wlxmlNode.end),
248                 _with: {tag: wlxmlNode.tag, klass: wlxmlNode.klass}
249             });
250         } else {
251             return DocumentElement.prototype.wrapWithNodeElement.call(this, wlxmlNode);
252         }
253     },
254     unwrap: function() {
255         var parent = this.parent();
256         if(parent.children().length === 1) {
257             var grandParent = parent.parent();
258             if(grandParent) {
259                 var grandParentChildren = grandParent.children(),
260                     idx = grandParent.childIndex(parent),
261                     prev = idx - 1 > -1 ? grandParentChildren[idx-1] : null,
262                     next = idx + 1 < grandParentChildren.length ? grandParentChildren[idx+1] : null;
263                 if(prev && next) {
264                     prev.setText(prev.getText() + this.getText() + next.getText());
265                     next.detach();
266                 } else if (prev || next) {
267                     var target = prev ? prev : next;
268                     target.setText(target.getText() + this.getText());
269                 } else {
270                     parent.after(this);
271                 }
272             } else {
273                 parent.after(this);
274             }
275             parent.detach();
276         }
277     },
278     split: function(params) {
279         var parentElement = this.parent(),
280             myIdx = parentElement.childIndex(this),
281             myCanvas = this.canvas,
282             passed = false,
283             succeedingChildren = [],
284             thisElement = this,
285             prefix = this.getText().substr(0, params.offset),
286             suffix = this.getText().substr(params.offset);
287
288         parentElement.children().forEach(function(child) {
289             if(passed)
290                 succeedingChildren.push(child);
291             if(child.sameNode(thisElement))
292                 passed = true;
293         });
294
295         if(prefix.length > 0)
296             this.setText(prefix);
297         else
298             this.detach();
299         
300         var newElement = DocumentNodeElement.create({tag: parentElement.getWlxmlTag(), klass: parentElement.getWlxmlClass()}, myCanvas);
301         parentElement.after(newElement);
302
303         if(suffix.length > 0)
304             newElement.append({text: suffix});
305         succeedingChildren.forEach(function(child) {
306             newElement.append(child);
307         });
308     }
309 });
310
311 return {
312     DocumentElement: DocumentElement,
313     DocumentNodeElement: DocumentNodeElement,
314     DocumentTextElement: DocumentTextElement
315 };
316
317 });