583d29fd64e8c21023c3b27fa48b268a2c029b09
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / HTMLArea / Editor / Framework.js
1 /**
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13 /**
14 * Framework is the visual component of the Editor and contains the tool bar, the iframe, the textarea and the status bar
15 */
16 define('TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/Framework',
17 ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
18 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Resizable',
19 'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM',
20 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/TYPO3',
21 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Event/Event',
22 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/Toolbar',
23 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/Iframe',
24 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/StatusBar'],
25 function (Util, Resizable, Dom, Typo3, Event, Toolbar, Iframe, Statusbar) {
26
27 /**
28 * Framework constructor
29 */
30 var Framework = function (config) {
31 Util.apply(this, config);
32 // Set some references
33 for (var i = 0, n = this.items.length; i < n; i++) {
34 var item = this.items[i];
35 item.framework = this;
36 this[item.itemId] = item;
37 }
38 // Monitor iframe becoming ready
39 var self = this;
40 Event.one(this.iframe, 'HTMLAreaEventIframeReady', function (event) { Event.stopEvent(event); self.onIframeReady(); return false; });
41 // Let the framefork render itself, but it will fail to do so if inside a hidden tab or inline element
42 if (!this.isNested || Typo3.allElementsAreDisplayed(this.nestedParentElements.sorted)) {
43 this.render(this.textArea.parentNode, this.textArea.id);
44 } else {
45 // Clone the array of nested tabs and inline levels instead of using a reference as HTMLArea.util.TYPO3.accessParentElements will modify the array
46 var parentElements = [].concat(this.nestedParentElements.sorted);
47 // Walk through all nested tabs and inline levels to get correct sizes
48 Typo3.accessParentElements(parentElements, 'args[0].render(args[0].textArea.parentNode, args[0].textArea.id)', [this]);
49 }
50 };
51
52 Framework.prototype = {
53
54 /**
55 * Render the framework
56 *
57 * @param object container: the container into which to insert the framework
58 * @param string position: the id of the child element of the container before which the framework should be inserted
59 * @return void
60 */
61 render: function (container, position) {
62 this.el = document.createElement('div');
63 if (this.id) {
64 this.el.setAttribute('id', this.id);
65 }
66 if (this.cls) {
67 this.el.setAttribute('class', this.cls);
68 }
69 var position = document.getElementById(position);
70 this.el = container.insertBefore(this.el, position);
71 for (var i = 0, n = this.items.length; i < n; i++) {
72 var item = this.items[i];
73 item.render(this.el);
74 }
75 this.rendered = true;
76 },
77
78 /**
79 * Get the element to which the framework is rendered
80 */
81 getEl: function () {
82 return this.el;
83 },
84
85 /**
86 * Initiate events monitoring
87 */
88 initEventListeners: function () {
89 var self = this;
90 // Make the framework resizable, if configured by the user
91 this.makeResizable();
92 // Monitor textArea container becoming shown or hidden as it may change the height of the status bar
93 Event.on(this.textAreaContainer, 'HTMLAreaEventTextAreaContainerShow', function(event) { Event.stopEvent(event); self.resizable ? self.onTextAreaShow() : self.onWindowResize(); return false; });
94 // Monitor iframe becoming shown or hidden as it may change the height of the status bar
95 Event.on(this.iframe, 'HTMLAreaEventIframeShow', function(event) { Event.stopEvent(event); self.resizable ? self.onIframeShow() : self.onWindowResize(); return false; });
96 // Monitor window resizing
97 Event.on(window, 'resize', function (event) { self.onWindowResize(); });
98 // If the textarea is inside a form, on reset, re-initialize the HTMLArea content and update the toolbar
99 var form = this.textArea.form;
100 if (form) {
101 if (typeof form.onreset === 'function') {
102 if (typeof form.htmlAreaPreviousOnReset === 'undefined') {
103 form.htmlAreaPreviousOnReset = [];
104 }
105 form.htmlAreaPreviousOnReset.push(form.onreset);
106 }
107 Event.on(form, 'reset', function (event) { return self.onReset(event); });
108 }
109 // Monitor editor being unloaded
110 Event.one(this.iframe.getIframeWindow(), 'unload', function (event) { return self.onBeforeDestroy(); });
111 },
112
113 /**
114 * editorId should be set in config
115 */
116 editorId: null,
117
118 /**
119 * Get a reference to the editor
120 */
121 getEditor: function() {
122 return RTEarea[this.editorId].editor;
123 },
124
125 /**
126 * Flag indicating whether the framework is inside a tab or inline element that may be hidden
127 * Should be set in config
128 */
129 isNested: false,
130
131 /**
132 * All nested tabs and inline levels in the sorting order they were applied
133 * Should be set in config
134 */
135 nestedParentElements: {},
136
137 /**
138 * Flag set to true when the framework is ready
139 */
140 ready: false,
141
142 /**
143 * All nested tabs and inline levels in the sorting order they were applied
144 * Should be set in config
145 */
146 nestedParentElements: {},
147
148 /**
149 * Whether the framework should be made resizable
150 * May be set in config
151 */
152 resizable: false,
153
154 /**
155 * Maximum height to which the framework may resized (in pixels)
156 * May be set in config
157 */
158 maxHeight: 2000,
159
160 /**
161 * Initial textArea dimensions
162 * Should be set in config
163 */
164 textAreaInitialSize: {
165 width: 0,
166 contextWidth: 0,
167 height: 0
168 },
169
170 /**
171 * Get the toolbar
172 */
173 getToolbar: function () {
174 return this.toolbar;
175 },
176
177 /**
178 * Get the iframe
179 */
180 getIframe: function () {
181 return this.iframe;
182 },
183
184 /**
185 * Get the textarea container
186 */
187 getTextAreaContainer: function () {
188 return this.textAreaContainer;
189 },
190
191 /**
192 * Get the status bar
193 */
194 getStatusBar: function () {
195 return this.statusBar;
196 },
197
198 /**
199 * Make the framework resizable, if configured
200 */
201 makeResizable: function () {
202 if (this.resizable) {
203 var self = this;
204 this.resizer = Resizable.makeResizable(this.getEl(), {
205 minHeight: 200,
206 minWidth: 300,
207 maxHeight: this.maxHeight,
208 stop: function (event, ui) { Event.stopEvent(event); self.onHtmlAreaResize(ui.size); return false; }
209 });
210 }
211 },
212
213 /**
214 * Resize the framework when the resizer handles are used
215 */
216 onHtmlAreaResize: function (size) {
217 Dom.setSize(this.getEl(), size);
218 this.onFrameworkResize();
219 },
220
221 /**
222 * Handle the window resize event
223 * Buffer the event for IE
224 */
225 onWindowResize: function () {
226 var self = this;
227 if (this.windowResizeTimeoutId) {
228 window.clearTimeout(this.windowResizeTimeoutId);
229 }
230 this.windowResizeTimeoutId = window.setTimeout(function () { self.doWindowResize(); }, 10);
231 },
232
233 /**
234 * Size the iframe according to initial textarea size as set by Page and User TSConfig
235 */
236 doWindowResize: function () {
237 if (!this.isNested || Typo3.allElementsAreDisplayed(this.nestedParentElements.sorted)) {
238 this.resizeFramework();
239 } else {
240 // Clone the array of nested tabs and inline levels instead of using a reference as HTMLArea.util.TYPO3.accessParentElements will modify the array
241 var parentElements = [].concat(this.nestedParentElements.sorted);
242 // Walk through all nested tabs and inline levels to get correct sizes
243 Typo3.accessParentElements(parentElements, 'args[0].resizeFramework()', [this]);
244 }
245 },
246
247 /**
248 * Resize the framework to its initial size
249 */
250 resizeFramework: function () {
251 var frameworkHeight = this.fullScreen ? Typo3.getWindowSize().height - 20 : parseInt(this.textAreaInitialSize.height) + this.toolbar.getHeight() - this.statusBar.getHeight();
252 if (this.textAreaInitialSize.width.indexOf('%') === -1) {
253 // Width is specified in pixels
254 // Initial framework sizing
255 var frameworkWidth = parseInt(this.textAreaInitialSize.width);
256 } else {
257 // Width is specified in %
258 // Framework sizing on actual window resize
259 var frameworkWidth = parseInt(((Typo3.getWindowSize().width - this.textAreaInitialSize.wizardsWidth - (this.fullScreen ? 10 : Util.getScrollBarWidth()) - Dom.getPosition(this.getEl()).x - 15) * parseInt(this.textAreaInitialSize.width))/100);
260 }
261 Dom.setSize(this.getEl(), { width: frameworkWidth, height: frameworkHeight});
262 this.onFrameworkResize();
263 },
264
265 /**
266 * Resize the framework components
267 */
268 onFrameworkResize: function () {
269 Dom.setSize(this.iframe.getEl(), { width: this.getInnerWidth(), height: this.getInnerHeight()});
270 Dom.setSize(this.textArea, { width: this.getInnerWidth(), height: this.getInnerHeight()});
271 },
272
273 /**
274 * Adjust the height to the changing size of the statusbar when the textarea is shown
275 */
276 onTextAreaShow: function () {
277 Dom.setSize(this.iframe.getEl(), { height: this.getInnerHeight()});
278 Dom.setSize(this.textArea, { width: this.getInnerWidth(), height: this.getInnerHeight()});
279 },
280
281 /**
282 * Adjust the height to the changing size of the statusbar when the iframe is shown
283 */
284 onIframeShow: function () {
285 if (this.getInnerHeight() <= 0) {
286 this.onWindowResize();
287 } else {
288 //this.iframe.setHeight(this.getInnerHeight());
289 Dom.setSize(this.iframe.getEl(), { height: this.getInnerHeight()});
290 Dom.setSize(this.textArea, { height: this.getInnerHeight()});
291 }
292 },
293
294 /**
295 * Calculate the height available for the editing iframe
296 */
297 getInnerHeight: function () {
298 return Dom.getSize(this.getEl()).height - this.toolbar.getHeight() - this.statusBar.getHeight() - 5;
299 },
300
301 /**
302 * Calculate the width available for the editing iframe
303 */
304 getInnerWidth: function () {
305 return Dom.getSize(this.getEl()).width;
306 },
307
308 /**
309 * Fire the editor when all components of the framework are rendered and ready
310 */
311 onIframeReady: function () {
312 this.ready = this.rendered && this.toolbar.rendered && this.statusBar.rendered && this.textAreaContainer.rendered;
313 if (this.ready) {
314 this.initEventListeners();
315 this.textAreaContainer.show();
316 if (!this.getEditor().config.showStatusBar) {
317 this.statusBar.hide();
318 }
319 // Set the initial size of the framework
320 this.onWindowResize();
321 /**
322 * @event HTMLAreaEventFrameworkReady
323 * Fires when the iframe is ready and all components are rendered
324 */
325 Event.trigger(this, 'HTMLAreaEventFrameworkReady');
326 } else {
327 var self = this;
328 window.setTimeout(function () {
329 self.onIframeReady();
330 }, 50);
331 }
332 },
333
334 /**
335 * Handler invoked if we are inside a form and the form is reset
336 * On reset, re-initialize the HTMLArea content and update the toolbar
337 */
338 onReset: function (event) {
339 this.getEditor().setHTML(this.textArea.value);
340 this.toolbar.update();
341 // Invoke previous reset handlers, if any
342 var htmlAreaPreviousOnReset = event.target.htmlAreaPreviousOnReset;
343 if (typeof htmlAreaPreviousOnReset !== 'undefined') {
344 for (var i = 0, n = htmlAreaPreviousOnReset.length; i < n; i++) {
345 htmlAreaPreviousOnReset[i]();
346 }
347 }
348 return true;
349 },
350
351 /**
352 * Cleanup on framework destruction
353 */
354 onBeforeDestroy: function () {
355 Event.off(window);
356 Event.off(this.iframe);
357 Event.off(this.textAreaContainer);
358 // Cleaning references to DOM in order to avoid IE memory leaks
359 var form = this.textArea.form;
360 if (form) {
361 Event.off(form);
362 form.htmlAreaPreviousOnReset = null;
363 }
364 if (this.resizer) {
365 Resizable.destroy(this.resizer);
366 }
367 this.el = null;
368 return true;
369 }
370 };
371
372 return Framework;
373
374 });