1da7b3e55fb5e6995c26e71f0fb5e7d0f043055a
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / documentElement.js
1 define([
2 'libs/jquery',
3 'libs/underscore',
4 'modules/documentCanvas/canvas/utils'
5 ], function($, _, utils) {
6     
7 'use strict';
8 /* global Node:false */
9
10 // DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
11 var DocumentElement = function(wlxmlNode, canvas) {
12     this.wlxmlNode = wlxmlNode;
13     this.canvas = canvas;
14
15     this.$element = this.createDOM();
16     this.$element.data('canvas-element', this);
17 };
18
19 $.extend(DocumentElement.prototype, {
20     refreshPath: function() {
21         this.parents().forEach(function(parent) {
22             parent.refresh();
23         });
24         this.refresh();
25     },
26     refresh: function() {
27         // noop
28     },
29     dom: function() {
30         return this.$element;
31     },
32     parent: function() {
33         var parents = this.$element.parents('[document-node-element]');
34         if(parents.length) {
35             return this.canvas.getDocumentElement(parents[0]);
36         }
37         return null;
38     },
39
40     parents: function() {
41         var parents = [],
42             parent = this.parent();
43         while(parent) {
44             parents.push(parent);
45             parent = parent.parent();
46         }
47         return parents;
48     },
49
50     sameNode: function(other) {
51         return other && (typeof other === typeof this) && other.dom()[0] === this.dom()[0];
52     },
53
54     getPreviousTextElement: function(includeInvisible) {
55         return this.getNearestTextElement('above', includeInvisible);
56     },
57
58     getNextTextElement: function(includeInvisible) {
59         return this.getNearestTextElement('below', includeInvisible);
60     },
61
62     getNearestTextElement: function(direction, includeInvisible) {
63         includeInvisible = includeInvisible !== undefined ? includeInvisible : false;
64         var selector = '[document-text-element]' + (includeInvisible ? '' : ':visible');
65         return this.canvas.getDocumentElement(utils.nearestInDocumentOrder(selector, direction, this.dom()[0]));
66     },
67
68     trigger: function() {
69         //this.canvas.bus.trigger()
70     }
71
72
73 });
74
75
76 // DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
77 var DocumentNodeElement = function(wlxmlNode, canvas) {
78     DocumentElement.call(this, wlxmlNode, canvas);
79     wlxmlNode.setData('canvasElement', this);
80     this.init(this.$element);
81 };
82
83
84 var manipulate = function(e, params, action) {
85     var element;
86     if(params instanceof DocumentElement) {
87         element = params;
88     } else {
89         element = e.canvas.createElement(params);
90     }
91     e.dom()[action](element.dom());
92     e.refreshPath();
93     return element;
94 };
95
96 DocumentNodeElement.prototype = Object.create(DocumentElement.prototype);
97
98
99 $.extend(DocumentNodeElement.prototype, {
100     defaultDisplayStyle: 'block',
101     addWidget: function(widget) {
102         this.$element.children('.canvas-widgets').append(widget.DOM ? widget.DOM : widget);
103     },
104     clearWidgets: function() {
105         this.$element.children('.canvas-widgets').empty();
106     },
107     handle: function(event) {
108         var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1);
109         if(this[method]) {
110             this[method](event);
111         }
112     },
113     createDOM: function() {
114         var wrapper = $('<div>').attr('document-node-element', ''),
115             widgetsContainer = $('<div>')
116                 .addClass('canvas-widgets')
117                 .attr('contenteditable', false),
118             contentContainer = $('<div>')
119                 .attr('document-element-content', '');
120         
121         wrapper.append(widgetsContainer, contentContainer);
122         widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
123         return wrapper;
124     },
125     _container: function() {
126         return this.dom().children('[document-element-content]');
127     },
128     detach: function() {
129         var parents = this.parents();
130         this.dom().detach();
131         this.canvas = null;
132         if(parents[0]) {
133             parents[0].refreshPath();
134         }
135          return this;
136     },
137     before: function(params) {
138         return manipulate(this, params, 'before');
139
140     },
141     after: function(params) {
142         return manipulate(this, params, 'after');
143     },
144
145     toggleLabel: function(toggle) {
146         var displayCss = toggle ? 'inline-block' : 'none';
147         var label = this.dom().children('.canvas-widgets').find('.canvas-widget-label');
148         label.css('display', displayCss);
149         this.toggleHighlight(toggle);
150     },
151
152     toggleHighlight: function(toggle) {
153         this._container().toggleClass('highlighted-element', toggle);
154     },
155
156     isBlock: function() {
157         return this.dom().css('display') === 'block';
158     },
159
160     displayAsBlock: function() {
161         this.dom().css('display', 'block');
162         this._container().css('display', 'block');
163     },
164     displayInline: function() {
165         this.dom().css('display', 'inline');
166         this._container().css('display', 'inline');
167     },
168     displayAs: function(what) {
169         // [this.dom(), this._container()].forEach(e) {
170         //     var isBlock = window.getComputedStyle(e).display === 'block';
171         //     if(!isBlock && what === 'block') {
172         //         e.css('display', what);
173         //     } else if(isBlock && what === 'inline') {
174         //         e.css('display')
175         //     }
176         // })
177         this.dom().css('display', what);
178         this._container().css('display', what);
179     }
180 });
181
182
183 // DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
184 var DocumentTextElement = function(wlxmlTextNode, canvas) {
185     DocumentElement.call(this, wlxmlTextNode, canvas);
186 };
187
188 $.extend(DocumentTextElement, {
189     isContentContainer: function(htmlElement) {
190         return htmlElement.nodeType === Node.TEXT_NODE && $(htmlElement).parent().is('[document-text-element]');
191     }
192 });
193
194 DocumentTextElement.prototype = Object.create(DocumentElement.prototype);
195
196 $.extend(DocumentTextElement.prototype, {
197     createDOM: function() {
198         var dom = $('<div>')
199             .attr('document-text-element', '')
200             .text(this.wlxmlNode.getText() || utils.unicode.ZWS);
201         return dom;
202     },
203     detach: function() {
204         this.dom().detach();
205         this.canvas = null;
206         return this;
207     },
208     setText: function(text) {
209         this.dom().contents()[0].data = text;
210     },
211     getText: function(options) {
212         options = _.extend({raw: false}, options || {});
213         var toret = this.dom().text();
214         if(!options.raw) {
215             toret = toret.replace(utils.unicode.ZWS, '');
216         }
217         return toret;
218     },
219     isEmpty: function() {
220         // Having at least Zero Width Space is guaranteed be Content Observer
221         return this.dom().contents()[0].data === utils.unicode.ZWS;
222     },
223     after: function(params) {
224         if(params instanceof DocumentTextElement || params.text) {
225             return false;
226         }
227         var element;
228         if(params instanceof DocumentNodeElement) {
229             element = params;
230         } else {
231             element = this.canvas.createElement(params);
232         }
233         this.dom().wrap('<div>');
234         this.dom().parent().after(element.dom());
235         this.dom().unwrap();
236         this.refreshPath();
237         return element;
238     },
239     before: function(params) {
240         if(params instanceof DocumentTextElement || params.text) {
241             return false;
242         }
243         var element;
244         if(params instanceof DocumentNodeElement) {
245             element = params;
246         } else {
247             element = this.canvas.createElement(params);
248         }
249         this.dom().wrap('<div>');
250         this.dom().parent().before(element.dom());
251         this.dom().unwrap();
252         this.refreshPath();
253         return element;
254     },
255
256     toggleHighlight: function() {
257         // do nothing for now
258     },
259     children: function() {
260         return [];
261     }
262
263 });
264
265
266 return {
267     DocumentElement: DocumentElement,
268     DocumentNodeElement: DocumentNodeElement,
269     DocumentTextElement: DocumentTextElement
270 };
271
272 });