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