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