878e140cab5734ebbd8f0c85d160ef6dc0cc115b
[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, this.canvas.metadata.user);
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 node = event.meta.node,
158             toSet = node.getText(),
159             handled;
160         
161         handled = this.children().some(function(child) {
162             if(child.wlxmlNode.sameNode(node)) {
163                 if(toSet === '') {
164                     toSet = utils.unicode.ZWS;
165                 }
166                 if(toSet !== child.getText()) {
167                     child.setText(toSet);
168                 }
169                 return true;
170             }
171         });
172
173         if(!handled && node.parent() && node.parent().is('comment') && this.wlxmlNode.sameNode(node.parent().parent())) {
174             this.commentsView.render();
175         }
176     },
177
178     onStateChange: function(changes) {
179         if(_.isBoolean(changes.exposed) && !this.isSpan()) {
180             this._container().toggleClass('highlighted-element', changes.exposed);
181         }
182         if(_.isBoolean(changes.active) && !this.isSpan()) {
183             this._container().toggleClass('current-node-element', changes.active);
184         }
185     },
186
187     ///
188
189     isSpan: function() {
190         return this.wlxmlNode.getTagName() === 'span';
191     },
192     
193     containsBlock: function() {
194         return this.children()
195             .filter(function(child) {
196                 return child instanceof documentElement.DocumentNodeElement;
197             })
198             .some(function(child) {
199                 if(child.isBlock()) {
200                     return true;
201                 } else {
202                     return child.containsBlock();
203                 }
204             });
205     },
206
207     prepend: function(param) {
208         var element;
209         if(param instanceof documentElement.DocumentElement) {
210             element = param;
211         } else {
212             element = this.canvas.createElement(param);
213         }
214         if(element.dom) {
215             this._container().prepend(element.dom);
216             this.refreshPath();
217         }
218         return element;
219     },
220
221     childIndex: function(child) {
222         var children = this.children(),
223             toret = null;
224         children.forEach(function(c, idx) {
225             if(c.sameNode(child)) {
226                 toret = idx;
227                 return false;
228             }
229         });
230         return toret;
231     },
232
233     getWlxmlClass: function() {
234         var klass = this._container().attr('wlxml-class');
235         if(klass) {
236             return klass.replace(/-/g, '.');
237         }
238         return undefined;
239     },
240     setWlxmlClass: function(klass) {
241         if(klass === this.getWlxmlClass()) {
242             return;
243         }
244         if(klass) {
245             this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
246         }
247         else {
248             this._container().removeAttr('wlxml-class');
249         }
250         this.refreshPath();
251     }
252 });
253
254
255 return generic;
256
257
258
259 });