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