a7e6f927c5cc2086c7cf99970f5fac6736bf55a7
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / HTMLArea / Editor / StatusBar.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 /**
15 * The optional status bar at the bottom of the editor framework
16 */
17 define('TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/StatusBar',
18 ['TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
19 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
20 'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM',
21 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Event/Event'],
22 function (UserAgent, Util, Dom, Event) {
23
24 /**
25 * Status bar constructor
26 */
27 var StatusBar = function (config) {
28 Util.apply(this, config);
29 };
30
31 StatusBar.prototype = {
32
33 /**
34 * Render the status bar (called by framework rendering)
35 *
36 * @param object container: the container into which to insert the status bar (that is the framework)
37 * @return void
38 */
39 render: function (container) {
40 this.el = document.createElement('div');
41 if (this.id) {
42 this.el.setAttribute('id', this.id);
43 }
44 if (this.cls) {
45 this.el.setAttribute('class', this.cls);
46 }
47 this.el = container.appendChild(this.el);
48 this.addComponents();
49 this.rendered = true;
50 this.initEventListeners();
51 },
52
53 /**
54 * Initialize listeners (after rendering)
55 */
56 initEventListeners: function () {
57 var self = this;
58 // Monitor toolbar updates in order to refresh the contents of the statusbar
59 // The toolbar must have been rendered
60 Event.on(this.framework.toolbar, 'HTMLAreaEventToolbarUpdate', function (event, mode, selectionEmpty, ancestors, endPointsInSameBlock) { Event.stopEvent(event); self.onUpdateToolbar(mode, selectionEmpty, ancestors, endPointsInSameBlock); return false; });
61 // Monitor editor changing mode
62 Event.on(this.getEditor(), 'HTMLAreaEventModeChange', function (event, mode) { Event.stopEvent(event); self.onModeChange(mode); return false; });
63 // Monitor word count change
64 Event.on(this.framework.iframe, 'HTMLAreaEventWordCountChange', function (event, delay) { Event.stopEvent(event); self.onWordCountChange(delay); return false; });
65 // Monitor editor being unloaded
66 Event.one(this.framework.iframe.getIframeWindow(), 'unload', function (event) { return self.onBeforeDestroy(); });
67 },
68
69 /**
70 * Get the element to which the status bar is rendered
71 */
72 getEl: function () {
73 return this.el;
74 },
75
76 /**
77 * Get the current height of the status bar
78 */
79 getHeight: function () {
80 return Dom.getSize(this.el).height;
81 },
82
83 /**
84 * editorId should be set in config
85 */
86 editorId: null,
87
88 /**
89 * Get a reference to the editor
90 */
91 getEditor: function() {
92 return RTEarea[this.editorId].editor;
93 },
94
95 /**
96 * Create span elements to display when the status bar tree or a message when the editor is in text mode
97 */
98 addComponents: function () {
99 // Word count
100 var wordCount = document.createElement('span');
101 wordCount.id = this.editorId + '-statusBarWordCount';
102 wordCount.style.display = 'block';
103 wordCount.innerHTML = ' ';
104 Dom.addClass(wordCount, 'statusBarWordCount');
105 this.statusBarWordCount = this.getEl().appendChild(wordCount);
106 // Element tree
107 var tree = document.createElement('span');
108 tree.id = this.editorId + '-statusBarTree';
109 tree.style.display = 'block';
110 tree.innerHTML = HTMLArea.localize('Path') + ': ';
111 Dom.addClass(tree, 'statusBarTree');
112 this.statusBarTree = this.getEl().appendChild(tree);
113 // Text mode
114 var textMode = document.createElement('span');
115 textMode.id = this.editorId + '-statusBarTextMode';
116 textMode.style.display = 'none';
117 textMode.innerHTML = HTMLArea.localize('TEXT_MODE');
118 Dom.addClass(textMode, 'statusBarTextMode');
119 this.statusBarTextMode = this.getEl().appendChild(textMode);
120 },
121
122 /**
123 * Clear the status bar tree
124 */
125 clear: function () {
126 var node;
127 while (node = this.statusBarTree.firstChild) {
128 if (/^(a)$/i.test(node.nodeName)) {
129 Event.off(node);
130 node.removeAttribute('ext:qtitle');
131 node.removeAttribute('ext:qtip');
132 }
133 Dom.removeFromParent(node);
134 }
135 this.setSelection(null);
136 },
137
138 /**
139 * Flag indicating that the status bar should not be updated on this toolbar update
140 */
141 noUpdate: false,
142
143 /**
144 * Update the status bar when the toolbar was updated
145 *
146 * @return void
147 */
148 onUpdateToolbar: function (mode, selectionEmpty, ancestors, endPointsInSameBlock) {
149 if (mode === 'wysiwyg' && !this.noUpdate) {
150 var self = this;
151 var text,
152 language,
153 languageObject = this.getEditor().getPlugin('Language'),
154 classes = new Array(),
155 classText;
156 this.clear();
157 var path = document.createElement('span');
158 path.innerHTML = HTMLArea.localize('Path') + ': ';
159 path = this.statusBarTree.appendChild(path);
160 var index, n, j, m;
161 for (index = 0, n = ancestors.length; index < n; index++) {
162 var ancestor = ancestors[index];
163 if (!ancestor) {
164 continue;
165 }
166 text = ancestor.nodeName.toLowerCase();
167 // Do not show any id generated by ExtJS
168 if (ancestor.id && text !== 'body' && ancestor.id.substr(0, 7) !== 'ext-gen') {
169 text += '#' + ancestor.id;
170 }
171 if (languageObject && languageObject.getLanguageAttribute) {
172 language = languageObject.getLanguageAttribute(ancestor);
173 if (language != 'none') {
174 text += '[' + language + ']';
175 }
176 }
177 if (ancestor.className) {
178 classText = '';
179 classes = ancestor.className.trim().split(' ');
180 for (j = 0, m = classes.length; j < m; ++j) {
181 if (!HTMLArea.reservedClassNames.test(classes[j])) {
182 classText += '.' + classes[j];
183 }
184 }
185 text += classText;
186 }
187 var element = document.createElement('a');
188 element.href = '#';
189 element.setAttribute('ext:qtitle', HTMLArea.localize('statusBarStyle'));
190 element.setAttribute('ext:qtip', ancestor.style.cssText.split(';').join('<br />'));
191 element.innerHTML = text;
192 element = path.parentNode.insertBefore(element, path.nextSibling);
193 element.ancestor = ancestor;
194 Event.on(element, 'click', function (event) { return self.onClick(event); });
195 Event.on(element, 'mousedown', function (event) { return self.onClick(event); });
196 if (!UserAgent.isOpera) {
197 Event.on(element, 'contextmenu', function (event) { return self.onContextMenu(event); });
198 }
199 if (index) {
200 var separator = document.createElement('span');
201 separator.innerHTML = String.fromCharCode(0xbb);
202 element.parentNode.insertBefore(separator, element.nextSibling);
203 }
204 }
205 }
206 this.updateWordCount();
207 this.noUpdate = false;
208 },
209
210 /**
211 * Handler when the word count may have changed
212 *
213 * @param integer delay: the delay before updating the word count
214 * @return void
215 */
216 onWordCountChange: function (delay) {
217 if (this.updateWordCountLater) {
218 window.clearTimeout(this.updateWordCountLater);
219 }
220 if (delay) {
221 var self = this;
222 this.updateWordCountLater = window.setTimeout(function () {
223 self.updateWordCount();
224 }, delay);
225 } else {
226 this.updateWordCount();
227 }
228 },
229
230 /**
231 * Update the word count
232 */
233 updateWordCount: function() {
234 var wordCount = 0;
235 if (this.getEditor().getMode() == 'wysiwyg') {
236 // Get the html content
237 var text = this.getEditor().getHTML();
238 if (typeof text === 'string' && text.length > 0) {
239 // Replace html tags with spaces
240 text = text.replace(HTMLArea.RE_htmlTag, ' ');
241 // Replace html space entities
242 text = text.replace(/&nbsp;|&#160;/gi, ' ');
243 // Remove numbers and punctuation
244 text = text.replace(HTMLArea.RE_numberOrPunctuation, '');
245 // Get the number of word
246 wordCount = text.split(/\S\s+/g).length - 1;
247 }
248 }
249 // Update the word count of the status bar
250 this.statusBarWordCount.innerHTML = wordCount ? ( wordCount + ' ' + HTMLArea.localize((wordCount == 1) ? 'word' : 'words')) : '&nbsp;';
251 },
252
253 /**
254 * Adapt status bar to current editor mode
255 *
256 * @param string mode: the mode to which the editor got switched to
257 * @return void
258 */
259 onModeChange: function (mode) {
260 switch (mode) {
261 case 'wysiwyg':
262 this.statusBarTextMode.style.display = 'none';
263 this.statusBarTree.style.display = 'block';
264 break;
265 case 'textmode':
266 default:
267 this.statusBarTree.style.display = 'none';
268 this.statusBarTextMode.style.display = 'block';
269 break;
270 }
271 },
272
273 /**
274 * Reference to the element last selected on the status bar
275 */
276 selected: null,
277
278 /**
279 * Get the status bar selection
280 */
281 getSelection: function() {
282 return this.selected;
283 },
284
285 /**
286 * Set the status bar selection
287 *
288 * @param object element: set the status bar selection to the given element
289 */
290 setSelection: function (element) {
291 this.selected = element ? element : null;
292 },
293
294 /**
295 * Select the element that was clicked in the status bar and set the status bar selection
296 */
297 selectElement: function (element) {
298 var editor = this.getEditor();
299 element.blur();
300 if (!UserAgent.isIEBeforeIE9) {
301 if (/^(img|table)$/i.test(element.ancestor.nodeName)) {
302 editor.getSelection().selectNode(element.ancestor);
303 } else {
304 editor.getSelection().selectNodeContents(element.ancestor);
305 }
306 } else {
307 if (/^(img|table)$/i.test(element.ancestor.nodeName)) {
308 var range = editor.document.body.createControlRange();
309 range.addElement(element.ancestor);
310 range.select();
311 } else {
312 editor.getSelection().selectNode(element.ancestor);
313 }
314 }
315 this.setSelection(element.ancestor);
316 this.noUpdate = true;
317 editor.toolbar.update();
318 },
319
320 /**
321 * Click handler
322 */
323 onClick: function (event) {
324 this.selectElement(event.target);
325 Event.stopEvent(event);
326 return false;
327 },
328
329 /**
330 * ContextMenu handler
331 */
332 onContextMenu: function (event) {
333 this.selectElement(event.target);
334 return this.getEditor().getPlugin('ContextMenu') ? this.getEditor().getPlugin('ContextMenu').show(event, event.target.ancestor) : false;
335 },
336
337 /**
338 * Cleanup
339 */
340 onBeforeDestroy: function() {
341 this.clear();
342 while (node = this.el.firstChild) {
343 this.el.removeChild(node);
344 }
345 this.statusBarTree = null;
346 this.statusBarWordCount = null;
347 this.el = null;
348 delete this.el;
349 return true;
350 }
351 };
352
353 return StatusBar;
354
355 });