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