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