editor: start using data api on text nodes for keeping references to canvas elements
[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
153         /* TODO: This handling of changes to the comments should probably be implemented via separate,
154         non-rendering comments element */
155         if(node.parent() && node.parent().is('comment') && this.wlxmlNode.sameNode(node.parent().parent())) {
156             this.commentsView.render();
157         }
158     },
159
160     onStateChange: function(changes) {
161         if(_.isBoolean(changes.exposed) && !this.isSpan()) {
162             this._container().toggleClass('highlighted-element', changes.exposed);
163         }
164         if(_.isBoolean(changes.active) && !this.isSpan()) {
165             this._container().toggleClass('current-node-element', changes.active);
166         }
167     },
168
169     ///
170
171     isSpan: function() {
172         return this.wlxmlNode.getTagName() === 'span';
173     },
174     
175     containsBlock: function() {
176         return this.children()
177             .filter(function(child) {
178                 return child instanceof documentElement.DocumentNodeElement;
179             })
180             .some(function(child) {
181                 if(child.isBlock()) {
182                     return true;
183                 } else {
184                     return child.containsBlock();
185                 }
186             });
187     },
188
189     prepend: function(param) {
190         var element;
191         if(param instanceof documentElement.DocumentElement) {
192             element = param;
193         } else {
194             element = this.canvas.createElement(param);
195         }
196         if(element.dom) {
197             this._container().prepend(element.dom);
198             this.refreshPath();
199         }
200         return element;
201     },
202
203     childIndex: function(child) {
204         var children = this.children(),
205             toret = null;
206         children.forEach(function(c, idx) {
207             if(c.sameNode(child)) {
208                 toret = idx;
209                 return false;
210             }
211         });
212         return toret;
213     }
214 });
215
216
217 return generic;
218
219
220
221 });