fnpjs: Action now triggers actionExecuted event instead of using callback
[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(params) {
84         var toret;
85         
86         params = _.extend({
87             considerChildren: true
88         }, params);
89         
90         this.children().some(function(child) {
91             if(child instanceof documentElement.DocumentTextElement) {
92                 toret = child;
93                 return true; // break
94             } else if(params.considerChildren) {
95                 toret = child.getVerticallyFirstTextElement();
96                 if(toret) {
97                     return true; // break
98                 }
99             }
100         });
101         return toret;
102     },
103
104     onNodeAttrChange: function(event) {
105         if(event.meta.attr === 'class') {
106             this.setWlxmlClass(event.meta.newVal); //
107         }
108     },
109     onNodeAdded: function(event) {
110         if(event.meta.node.isRoot()) {
111             this.canvas.reloadRoot();
112             return;
113         }
114
115         var ptr = event.meta.node.prev(),
116             referenceElement, referenceAction, actionArg;
117             
118         while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr))) {
119             ptr = ptr.prev();
120         }
121
122         if(referenceElement) {
123             referenceAction = 'after';
124         } else {
125             referenceElement = this;
126             referenceAction = 'prepend';
127         }
128       
129         if(event.meta.move) {
130             /* Let's check if this node had its own canvas element and it's accessible. */
131             actionArg = utils.getElementForElementRootNode(event.meta.node);
132         }
133         if(!actionArg) {
134             actionArg = event.meta.node;
135         }
136
137         referenceElement[referenceAction](actionArg);
138
139         if(event.meta.node.is('comment')) {
140             this.commentTip.show();
141             this.commentsView.render();
142         }
143     },
144     onNodeDetached: function(event) {
145         var isComment = event.meta.node.is('comment');
146         if(event.meta.node.sameNode(this)) {
147             this.detach();
148         } else {
149             this.children().some(function(child) {
150                 if(child.wlxmlNode.sameNode(event.meta.node)) {
151                     child.detach();
152                     return true;
153                 }
154             });
155             if(isComment && !this.wlxmlNode.hasChild({klass: 'comment'})) {
156                 this.commentTip.hide();
157             }
158             this.commentsView.render();
159         }
160     },
161     onNodeTextChange: function(event) {
162         var node = event.meta.node,
163             toSet = node.getText(),
164             handled;
165         
166         handled = this.children().some(function(child) {
167             if(child.wlxmlNode.sameNode(node)) {
168                 if(toSet === '') {
169                     toSet = utils.unicode.ZWS;
170                 }
171                 if(toSet !== child.getText()) {
172                     child.setText(toSet);
173                 }
174                 return true;
175             }
176         });
177
178         if(!handled && node.parent() && node.parent().is('comment') && this.wlxmlNode.sameNode(node.parent().parent())) {
179             this.commentsView.render();
180         }
181     },
182
183     onStateChange: function(changes) {
184         if(_.isBoolean(changes.exposed) && !this.isSpan()) {
185             this._container().toggleClass('highlighted-element', changes.exposed);
186         }
187         if(_.isBoolean(changes.active) && !this.isSpan()) {
188             this._container().toggleClass('current-node-element', changes.active);
189         }
190     },
191
192     ///
193
194     isSpan: function() {
195         return this.wlxmlNode.getTagName() === 'span';
196     },
197     
198     containsBlock: function() {
199         return this.children()
200             .filter(function(child) {
201                 return child instanceof documentElement.DocumentNodeElement;
202             })
203             .some(function(child) {
204                 if(child.isBlock()) {
205                     return true;
206                 } else {
207                     return child.containsBlock();
208                 }
209             });
210     },
211
212     prepend: function(param) {
213         var element;
214         if(param instanceof documentElement.DocumentElement) {
215             element = param;
216         } else {
217             element = this.canvas.createElement(param);
218         }
219         if(element.dom) {
220             this._container().prepend(element.dom);
221             this.refreshPath();
222         }
223         return element;
224     },
225
226     childIndex: function(child) {
227         var children = this.children(),
228             toret = null;
229         children.forEach(function(c, idx) {
230             if(c.sameNode(child)) {
231                 toret = idx;
232                 return false;
233             }
234         });
235         return toret;
236     },
237
238     getWlxmlClass: function() {
239         var klass = this._container().attr('wlxml-class');
240         if(klass) {
241             return klass.replace(/-/g, '.');
242         }
243         return undefined;
244     },
245     setWlxmlClass: function(klass) {
246         if(klass === this.getWlxmlClass()) {
247             return;
248         }
249         if(klass) {
250             this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
251         }
252         else {
253             this._container().removeAttr('wlxml-class');
254         }
255         this.refreshPath();
256     }
257 });
258
259
260 return generic;
261
262
263
264 });