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