editor: canvas - support NullElements - elements that are not inserted into canvas
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / genericElement.js
1 define(function(require) {
2     
3 'use strict';
4
5 var $ = require('libs/jquery'),
6     _ = require('libs/underscore'),
7     documentElement = require('./documentElement'),
8     utils = require('./utils'),
9     wlxmlUtils = require('utils/wlxml');
10
11 var labelWidget = function(tag, klass) {
12     return $('<span>')
13         .addClass('canvas-widget canvas-widget-label')
14         .text(wlxmlUtils.getTagLabel(tag) + (klass ? ' / ' + wlxmlUtils.getClassLabel(klass) : ''));
15 };
16 void(labelWidget); // for linters; labelWidget is unused on purpose for now
17
18 var DocumentNodeElement = documentElement.DocumentNodeElement;
19
20 var generic = Object.create(DocumentNodeElement.prototype);
21
22 $.extend(generic, {
23     init: function() {
24         DocumentNodeElement.prototype.init.call(this);
25         this._container()
26             .attr('wlxml-tag', this.wlxmlNode.getTagName());
27         this.setWlxmlClass(this.wlxmlNode.getClass());
28         this.wlxmlNode.contents().forEach(function(node) {
29             var el = this.canvas.createElement(node);
30             if(el.dom) {
31                 this._container().append(el.dom);
32             }
33         }.bind(this));
34         this.refresh();
35     },
36     
37     refresh: function() {
38         if(this.wlxmlNode.getTagName() === 'span') {
39             if(this.containsBlock()) {
40                 this.displayAsBlock();
41             } else {
42                 this.displayInline();
43             }
44         } else {
45             this.displayAsBlock();
46         }
47     },
48
49     getFirst: function(e1, e2) {
50         var idx1 = this.childIndex(e1),
51             idx2 = this.childIndex(e2);
52         if(e1 === null || e2 === null) {
53             return undefined;
54         }
55         return idx1 <= idx2 ? e1: e2;
56     },
57
58     children: function() {
59         var element = this,
60             toret = [];
61         this._container().contents().each(function() {
62             var childElement = element.canvas.getDocumentElement(this);
63             if(childElement === undefined) {
64                 return true;
65             }
66
67             toret.push(childElement);
68         });
69         return toret;
70     },
71
72     getVerticallyFirstTextElement: function() {
73         var toret;
74         this.children().some(function(child) {
75             if(child instanceof documentElement.DocumentTextElement) {
76                 toret = child;
77                 return true; // break
78             } else {
79                 toret = child.getVerticallyFirstTextElement();
80                 if(toret) {
81                     return true; // break
82                 }
83             }
84         });
85         return toret;
86     },
87
88     onNodeAttrChange: function(event) {
89         if(event.meta.attr === 'class') {
90             this.setWlxmlClass(event.meta.newVal); //
91         }
92     },
93     onNodeAdded: function(event) {
94         if(event.meta.node.isRoot()) {
95             this.canvas.reloadRoot();
96             return;
97         }
98
99         var ptr = event.meta.node.prev(),
100             referenceElement, referenceAction, actionArg;
101             
102         while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr))) {
103             ptr = ptr.prev();
104         }
105
106         if(referenceElement) {
107             referenceAction = 'after';
108         } else {
109             referenceElement = this;
110             referenceAction = 'prepend';
111         }
112       
113         if(event.meta.move) {
114             /* Let's check if this node had its own canvas element and it's accessible. */
115             actionArg = utils.getElementForElementRootNode(event.meta.node);
116         }
117         if(!actionArg) {
118             actionArg = event.meta.node;
119         }
120
121         referenceElement[referenceAction](actionArg);
122     },
123     onNodeDetached: function(event) {
124         if(event.meta.node.sameNode(this)) {
125             this.detach();
126         } else {
127             this.children().some(function(child) {
128                 if(child.wlxmlNode.sameNode(event.meta.node)) {
129                     child.detach();
130                     return true;
131                 }
132             });
133         }
134     },
135     onNodeTextChange: function(event) {
136         var toSet = event.meta.node.getText();
137         this.children().some(function(child) {
138             if(child.wlxmlNode.sameNode(event.meta.node)) {
139                 if(toSet === '') {
140                     toSet = utils.unicode.ZWS;
141                 }
142                 if(toSet !== child.getText()) {
143                     child.setText(toSet);
144                 }
145                 return true;
146             }
147         });
148     },
149
150     onStateChange: function(changes) {
151         if(_.isBoolean(changes.exposed) && !this.isSpan()) {
152             this._container().toggleClass('highlighted-element', changes.exposed);
153         }
154         if(_.isBoolean(changes.active) && !this.isSpan()) {
155             this._container().toggleClass('current-node-element', changes.active);
156         }
157     },
158
159     ///
160
161     isSpan: function() {
162         return this.wlxmlNode.getTagName() === 'span';
163     },
164     
165     containsBlock: function() {
166         return this.children()
167             .filter(function(child) {
168                 return child instanceof documentElement.DocumentNodeElement;
169             })
170             .some(function(child) {
171                 if(child.isBlock()) {
172                     return true;
173                 } else {
174                     return child.containsBlock();
175                 }
176             });
177     },
178
179     prepend: function(param) {
180         var element;
181         if(param instanceof documentElement.DocumentElement) {
182             element = param;
183         } else {
184             element = this.canvas.createElement(param);
185         }
186         if(element.dom) {
187             this._container().prepend(element.dom);
188             this.refreshPath();
189         }
190         return element;
191     },
192
193     childIndex: function(child) {
194         var children = this.children(),
195             toret = null;
196         children.forEach(function(c, idx) {
197             if(c.sameNode(child)) {
198                 toret = idx;
199                 return false;
200             }
201         });
202         return toret;
203     },
204
205     getWlxmlClass: function() {
206         var klass = this._container().attr('wlxml-class');
207         if(klass) {
208             return klass.replace(/-/g, '.');
209         }
210         return undefined;
211     },
212     setWlxmlClass: function(klass) {
213         if(klass === this.getWlxmlClass()) {
214             return;
215         }
216         if(klass) {
217             this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
218         }
219         else {
220             this._container().removeAttr('wlxml-class');
221         }
222         this.refreshPath();
223     }
224 });
225
226
227 return generic;
228
229
230
231 });