29ae76797b54009c096ad3176394395a23442287
[Packages/TYPO3.CMS.git] / typo3 / js / extjs / debugPanel.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2010-2011 Stefan Galinski <stefan.galinski@gmail.com>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 Ext.ns('TYPO3');
28
29 /**
30 * Debug panel based upon the widget tab panel
31 *
32 * If you want to add a new tab, you can use the addTab or addTabWidget methods. The first one
33 * creates the widget itself. If you need the latter one, you must create the widget yourself.
34 *
35 * The drag&drop functionality introduced a new attribute for the widget that should be added
36 * as a tab. It's called "draggableTab" and needs to be set to true, if you want activated
37 * drag&drop for the new tab.
38 *
39 * Additional Features:
40 * - Drag&Drop
41 * - Close tabs with a simple wheel/middle click
42 * - utilization of the tabCloseMenu context menu (several closing options)
43 * - Grouping of tabs (Only one nested level allowed!)
44 *
45 * @author Stefan Galinski <stefan.galinski@gmail.com>
46 */
47 TYPO3.DebugPanel = Ext.extend(Ext.TabPanel, {
48 /**
49 * Tab Groups
50 *
51 * @var Ext.util.MixedCollection
52 */
53 tabGroups: new Ext.util.MixedCollection(),
54
55 /**
56 * Indicator if the debug panel is wrapped inside a debug panel
57 * @see addTabWidget()
58 *
59 * @var boolean
60 */
61 isTabChildren: false,
62
63 /**
64 * Initializes the widget and merges our defaults with the user-defined ones. The
65 * user-defined settings are preferred.
66 *
67 * @return void
68 */
69 initComponent: function(config) {
70 config = config || {};
71 Ext.apply(this, config, {
72 // activate general tab navigation with mouse wheel support
73 enableTabScroll: true,
74 defaults: {
75 autoScroll: true
76 },
77
78 // add the context menu actions
79 plugins: new Ext.ux.TabCloseMenu({
80 closeTabText: TYPO3.LLL.core.tabs_close,
81 closeOtherTabsText: TYPO3.LLL.core.tabs_closeOther,
82 closeAllTabsText: TYPO3.LLL.core.tabs_closeAll,
83 customMenuEntries: [
84 '-',
85 {
86 itemId: 'openInBrowserWindow',
87 text: TYPO3.LLL.core.tabs_openInBrowserWindow,
88 scope: this,
89 handler: function() {
90 var tab = this.plugins.active;
91 var group = '', content = '';
92
93 if (tab.ownerCt.ownerCt instanceof Ext.TabPanel) {
94 group = tab.ownerCt.title;
95 content = tab.body.dom.innerHTML;
96 } else {
97 group = tab.title;
98 tab.items.each(function(item) {
99 content += item.body.dom.innerHTML;
100 });
101 }
102
103 this.openBrowserWindow(
104 tab.title,
105 content,
106 group
107 );
108 }
109 }
110 ]
111 })
112 });
113
114
115 TYPO3.DebugPanel.superclass.initComponent.call(this);
116 },
117
118 /**
119 * Create a drop arrow indicator for the tab drag&drop feature while rendering
120 * the component
121 *
122 * @return void
123 */
124 onRender: function() {
125 this.arrow = Ext.DomHelper.append(
126 Ext.getBody(),
127 '<div class="typo3-debugPanel-dragDropArrowDown">&nbsp;</div>',
128 true
129 );
130 this.arrow.hide();
131
132 TYPO3.DebugPanel.superclass.onRender.apply(this, arguments);
133 },
134
135 /**
136 * Collapse event
137 *
138 * @return void
139 */
140 onCollapse: function() {
141 TYPO3.DebugPanel.superclass.onCollapse.apply(this, arguments);
142 },
143
144 /**
145 * Expand event
146 *
147 * @return void
148 */
149 onExpand: function() {
150 TYPO3.DebugPanel.superclass.onExpand.apply(this, arguments);
151 },
152
153 /**
154 * Cleanup
155 *
156 * @return void
157 */
158 onDestroy: function() {
159 Ext.destroy(this.arrow);
160 TYPO3.DebugPanel.superclass.onDestroy.call(this);
161 },
162
163 /**
164 * Adds a new tab inside a new debug console tab or inside a new browser window if the
165 * debugInWindow configuration variable is set.
166 *
167 * If you need more possibilites, you should use the addTabWidget method.
168 *
169 * @see addTabWidget()
170 * @param tabContent String content of the new tab
171 * @param header String tab header
172 * @param group String tab group
173 * @param position Integer position of the new tab
174 * @return void
175 */
176 addTab: function(tabContent, header, group, position) {
177 if (TYPO3.configuration.debugInWindow) {
178 this.openBrowserWindow(header, tabContent, group);
179 } else {
180 var tabWidget = new Ext.Panel({
181 title: header,
182 html: tabContent,
183 border: false,
184 autoScroll: true,
185 closable: true,
186 draggableTab: true
187 });
188
189 this.addTabWidget(tabWidget, group, position);
190 }
191 },
192
193 /**
194 * Adds a new tab to the widget
195 *
196 * You can inject any Ext component, but you need to create it yourself. If you just
197 * want to add some text into a new tab, you should use the addTab function.
198 *
199 * @see addTab()
200 * @param tabWidget Component the component that should be added as a new tab
201 * @param group String tab group
202 * @param position Integer position of the new tab
203 * @return void
204 */
205 addTabWidget: function(tabWidget, group, position) {
206 if (this.hidden) {
207 this.show();
208 } else if (this.collapsed) {
209 this.expand();
210 }
211
212 // Move the widget into a tab group?
213 var tabGroup = this;
214 if (typeof group !== 'undefined' && group !== '' && !this.isTabChildren) {
215 if (this.tabGroups.indexOfKey(group) === -1) {
216 tabGroup = new TYPO3.DebugPanel({
217 border: false,
218 title: group,
219 autoScroll: true,
220 closable: true,
221 isTabChildren: true,
222 tabParent: this,
223 draggableTab: true
224 });
225 this.addTabWidget(tabGroup);
226
227 this.tabGroups.add(group, tabGroup);
228 } else {
229 tabGroup = this.tabGroups.key(group);
230 }
231 }
232
233 // recalculate position if necessary
234 if (typeof position === 'undefined') {
235 position = tabGroup.items.getCount();
236 }
237
238 // hide the debug panel if the last element is closed
239 tabWidget.on('destroy', function(element) {
240 if (this.isTabChildren) {
241 if (!this.items.getCount()) {
242 this.tabParent.remove(this.tabParent.tabGroups.key(this.title));
243 }
244 } else {
245 if (!this.items.getCount()) {
246 this.hide();
247 this.ownerCt.doLayout();
248 }
249 this.tabGroups.removeKey(element.title);
250 }
251 }, tabGroup);
252
253 // add drag&drop and the wheel click functionality
254 tabWidget.on('afterlayout', function(element) {
255 Ext.get(this.id + '__' + element.id).on('mousedown', function(event) {
256 if (!Ext.isIE7) {
257 if ((Ext.isIE && event.button === 1) ||
258 (!Ext.isIE && event.browserEvent.button === 1)
259 ) {
260 event.stopEvent();
261 this.remove(tabWidget);
262 return false;
263 }
264 }
265 return true;
266 }, this);
267
268 if (tabWidget.draggableTab) {
269 this.initDragAndDropForTab(tabWidget);
270 }
271 }, tabGroup);
272
273 // add the widget as a new tab
274 tabGroup.insert(position, tabWidget).show();
275 tabGroup.ownerCt.doLayout();
276 },
277
278 /**
279 * Extends the tab item with drag&drop functionality.
280 *
281 * @param item Component the tab widget
282 * @return void
283 */
284 initDragAndDropForTab: function(item) {
285 item.tabDragZone = new Ext.dd.DragZone(this.id + '__' + item.id, {
286 ddGroup: this.id,
287
288 /**
289 * Reintroduces the simple click event on a tab element.
290 *
291 * @return void
292 */
293 b4MouseDown : function() {
294 item.show();
295 Ext.dd.DragZone.superclass.b4MouseDown.apply(this, arguments);
296 },
297
298 /**
299 * On receipt of a mousedown event, see if it is within a draggable element.
300 * Return a drag data object if so. The data object can contain arbitrary application
301 * data, but it should also contain a DOM element in the ddel property to provide
302 * a proxy to drag.
303 *
304 * @param event Ext.EventObject
305 * @return drag data
306 */
307 getDragData: function(event) {
308 var sourceElement = event.getTarget(item.itemSelector, 10);
309 if (sourceElement) {
310 var dragComponent = sourceElement.cloneNode(true);
311 dragComponent.id = Ext.id();
312 item.dragData = {
313 ddel: dragComponent,
314 sourceEl: sourceElement,
315 repairXY: Ext.fly(sourceElement).getXY()
316 };
317 return item.dragData;
318 }
319
320 return false;
321 },
322
323 /**
324 * Provide coordinates for the proxy to slide back to on failed drag.
325 * This is the original XY coordinates of the draggable element.
326 *
327 * @return x,y coordinations of the original component position
328 */
329 getRepairXY: function() {
330 return this.dragData.repairXY;
331 }
332 });
333
334 item.tabDropZone = new Ext.dd.DropZone(this.id + '__' + item.id, {
335 debugPanel: this,
336 ddGroup: this.id,
337
338 /**
339 * If the mouse is over a tab element, return that node. This is
340 * provided as the "target" parameter in all "onNodeXXXX" node event
341 * handling functions
342 *
343 * @param event Ext.EventObject
344 * @return the tab element or boolean false
345 */
346 getTargetFromEvent: function(event) {
347 var tabElement = Ext.get(event.getTarget()).findParentNode('li');
348 if (tabElement !== null) {
349 return tabElement;
350 }
351
352 return false;
353 },
354
355 /**
356 * On entry into a target node, highlight that node.
357 *
358 * @param target string id of the target element
359 * @return void
360 */
361 onNodeEnter : function(target) {
362 Ext.get(target).addClass('typo3-debugPanel-dragDropOver');
363 },
364
365 /**
366 * On exit from a target node, unhighlight that node.
367 *
368 * @param target string id of the target element
369 * @return void
370 */
371 onNodeOut : function(target) {
372 Ext.get(target).removeClass('typo3-debugPanel-dragDropOver');
373 this.debugPanel.arrow.hide();
374 },
375
376 /**
377 * While over a target node, return the default drop allowed class which
378 * places a "tick" icon into the drag proxy. Also the arrow position is
379 * recalculated.
380 *
381 * @param target string id of the target element
382 * @param proxy Ext.dd.DDProxy proxy element
383 * @param event Ext.EventObject
384 * @return default dropAllowed class or a boolean false
385 */
386 onNodeOver : function(target, proxy, event) {
387 // set arrow position
388 var element = Ext.get(target);
389 var left = 0;
390 var tabLeft = element.getX();
391 var tabMiddle = tabLeft + element.dom.clientWidth / 2;
392 var tabRight = tabLeft + element.dom.clientWidth;
393 if (event.getPageX() <= tabMiddle) {
394 left = tabLeft;
395 } else {
396 left = tabRight;
397 }
398 this.debugPanel.arrow.setTop(this.el.getY() - 8).setLeft(left - 9).show();
399
400 // drop allowed?
401 if (proxy.handleElId !== target.id) {
402 return Ext.dd.DropZone.prototype.dropAllowed;
403 }
404
405 return false;
406 },
407
408 /**
409 * On node drop we move the dragged tab element at the position of
410 * the dropped element.
411 *
412 * @param target string id of the target element
413 * @param proxy Ext.dd.DDProxy proxy element
414 * @param event Ext.EventObject
415 * @return true or false
416 */
417 onNodeDrop : function(target, proxy, event) {
418 if (proxy.handleElId === target.id) {
419 return false;
420 }
421
422 var dropPanelId = target.id.substring(this.debugPanel.id.length + 2);
423 var dragPanelId = proxy.handleElId.substring(this.debugPanel.id.length + 2);
424
425 var dropPanelPosition = this.debugPanel.items.indexOfKey(dropPanelId);
426 var dragPanelPosition = this.debugPanel.items.indexOfKey(dragPanelId);
427
428 if (dropPanelPosition !== undefined &&
429 dropPanelPosition !== -1 &&
430 dropPanelPosition <= this.debugPanel.items.getCount()
431 ) {
432 // calculate arrow position to decide if the elements needs
433 // to be inserted on the right or left
434 var element = Ext.get(target);
435 var tabMiddle = element.getX() + element.dom.clientWidth / 2;
436 if (dragPanelPosition > dropPanelPosition) {
437 if (event.getPageX() > tabMiddle) {
438 dropPanelPosition += 1;
439 }
440 } else {
441 if (event.getPageX() <= tabMiddle) {
442 dropPanelPosition -= 1;
443 }
444 }
445
446 var dropEl = this.debugPanel.remove(dragPanelId, false);
447 this.debugPanel.addTabWidget(dropEl, '', dropPanelPosition);
448 }
449
450 this.debugPanel.arrow.hide();
451 return true;
452 }
453 });
454 },
455
456 /**
457 * Opens debug output in a new browser window
458 *
459 * @param title string
460 * @param content string
461 * @param group string
462 * @return void
463 */
464 openBrowserWindow: function(title, content, group) {
465 var newWindow = window.open('', 'TYPO3DebugWindow_' + group,
466 'width=600,height=400,menubar=0,toolbar=1,status=0,scrollbars=1,resizable=1'
467 );
468 if (newWindow.document.body.innerHTML) {
469 Ext.DomHelper.insertHtml('beforeEnd', newWindow.document.body, '<hr>' + content);
470 } else {
471 newWindow.document.writeln(
472 '<html><head><title>Debug: ' + title + '(' + group + ')</title></head>'
473 + '<body bgcolor=white onLoad="self.focus()">'
474 + content
475 + '</body></html>'
476 );
477 }
478 newWindow.document.close()
479 },
480
481 /**
482 * Wrapper for console.log
483 *
484 * @return void
485 */
486 log: function() {
487 if (arguments.length) {
488 for (var i = 0; i < arguments.length; i++) {
489 this.debug(arguments[i], 'Log', 'Javascript Console');
490 }
491 }
492 },
493
494 /**
495 * Wrapper for console.info
496 *
497 * @return void
498 */
499 info: function() {
500 if (arguments.length) {
501 for (var i = 0; i < arguments.length; i++) {
502 this.debug(arguments[i], 'Info', 'Javascript Console');
503 }
504 }
505 },
506
507 /**
508 * Wrapper for console.warn
509 *
510 * @return void
511 */
512 warn: function() {
513 if (arguments.length) {
514 for (var i = 0; i < arguments.length; i++) {
515 this.debug(arguments[i], 'Warning', 'Javascript Console');
516 }
517 }
518 },
519
520 /**
521 * Wrapper for console.error
522 *
523 * @return void
524 */
525 error: function() {
526 if (arguments.length) {
527 for (var i = 0; i < arguments.length; i++) {
528 this.debug(arguments[i], 'Error', 'Javascript Console');
529 }
530 }
531 },
532
533 /**
534 * Debug output from javascript
535 *
536 * @param out mixed debug output
537 * @param header string
538 * @param group string
539 */
540 debug: function(out, header, group) {
541 var output = this.printObject(out);
542 this.addTab(output, header, group);
543 },
544
545 /**
546 * Converts any string/array/object to a string for printing purposes
547 *
548 * @param object object
549 * @param level integer recursion level counter (max. 3 levels)
550 * @param prefix string internal use!
551 * @return string
552 */
553 printObject: function(object, level, prefix) {
554 var result = '';
555
556 prefix = prefix || '';
557 level = level || 0;
558 if (level >= 3) {
559 return result;
560 }
561
562 var levelPadding = '';
563 for(var j = 0; j < level + 1; ++j) {
564 levelPadding += ' ';
565 }
566
567 if (typeof(object) === 'object') {
568 // Array / Objects
569 for (var item in object) {
570 var value = object[item];
571
572 if (typeof(value) === 'object') {
573 result += levelPadding + '"' + prefix + item + '" ...' + "\n";
574 result += this.printObject(value, level + 1, prefix + item + '.');
575 } else {
576 result += levelPadding + '"' + prefix + item +
577 '" => "' + value + '"' + "\n";
578 }
579 }
580 } else {
581 // Strings/Chars/Numbers etc.
582 result = '[' + typeof(object) + '] ' + object;
583 }
584
585 return '<pre>' + result + '</pre>';
586 },
587
588 /**
589 * Debug attached events of a given element (e.g. an Ext.Panel component)
590 *
591 * Note: This functionality should be used with an activated debug console like firebug!
592 *
593 * @param element object to fetch events from
594 * @return void
595 */
596 debugEvents: function(element) {
597 if (element) {
598 // debug events of element
599 Ext.util.Observable.capture(element, function() {
600 console.log(
601 'event "' + arguments[0] + '" was fired with the following arguments: '
602 );
603
604 for (var i = 1; i < arguments.length; ++i) {
605 console.log(' [' + i + '] ', arguments[i]);
606 }
607 });
608 } else {
609 // debug all events
610 Ext.util.Observable.prototype.fireEvent =
611 Ext.util.Observable.prototype.fireEvent.createInterceptor(function() {
612 console.log(arguments);
613 return true;
614 });
615 }
616 }
617 });
618
619 Ext.reg('typo3DebugPanel', TYPO3.DebugPanel);