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