Ext.ux.InfoPanel extension class code:
1 // vim: ts=4:sw=4:nu:fdc=4:nospell
2
3 // Create user extensions namespace (Ext.ux)
4 Ext.namespace('Ext.ux');
5
6 /**
7 * Ext.ux.InfoPanel Extension Class
8 *
9 * @author Ing. Jozef Sakalos
10 * @version $Id: Ext.ux.InfoPanel.js 153 2007-08-24 10:46:19Z jozo $
11 *
12 * @class Ext.ux.InfoPanel
13 * @extends Ext.ContentPanel
14 * @constructor
15 * Creates new Ext.ux.InfoPanel
16 * @param {String/HTMLElement/Element} el The container element for this panel
17 * @param {String/Object} config A string to set only the title or a config object
18 * @param {String} content (optional) Set the HTML content for this panel
19 * @cfg {Boolean} animate set to true to switch animation of expand/collapse on (defaults to undefined)
20 * @cfg {String} bodyClass css class added to the body in addition to the default class(es)
21 * @cfg {String/HTMLElement/Element} bodyEl This element is used as body of panel.
22 * @cfg {String} buttonPosition set this to 'left' to place expand button to the left of titlebar
23 * @cfg {Boolean} collapsed false to start with the expanded body (defaults to true)
24 * @cfg {String} collapsedIcon Path for icon to display in the title when panel is collapsed
25 * @cfg {Boolean} collapseOnUnpin unpinned panel is collapsed when possible (defaults to true)
26 * @cfg {Boolean} collapsible false to disable collapsibility (defaults to true)
27 * @cfg {Boolean} draggable true to allow panel dragging (defaults to undefined)
28 * @cfg {Float} duration Duration of animation in seconds (defaults to 0.35)
29 * @cfg {String} easingCollapse Easing to use for collapse animation (e.g. 'backIn')
30 * @cfg {String} easingExpand Easing to use for expand animation (e.g. 'backOut')
31 * @cfg {String} expandedIcon Path for icon to display in the title when panel is expanded
32 * @cfg {String} icon Path for icon to display in the title
33 * @cfg {Integer} minWidth minimal width in pixels of the resizable panel (defaults to 0)
34 * @cfg {Integer} maxWidth maximal width in pixels of the resizable panel (defaults to 9999)
35 * @cfg {Integer} minHeight minimal height in pixels of the resizable panel (defaults to 50)
36 * @cfg {Integer} maxHeight maximal height in pixels of the resizable panel (defaults to 9999)
37 * @cfg {String} panelClass Set to override the default 'x-dock-panel' class.
38 * @cfg {Boolean} pinned true to start in pinned state (implies collapsed:false) (defaults to false)
39 * @cfg {Boolean} resizable true to allow use resize width of the panel. (defaults to undefined)
40 * Handles are transparent. (defaults to false)
41 * @cfg {String} shadowMode defaults to 'sides'.
42 * @cfg {Boolean} showPin Show the pin button - makes sense only if panel is part of Accordion
43 * @cfg {String} trigger 'title' or 'button'. Click where expands/collapses the panel (defaults to 'title')
44 * @cfg {Boolean} useShadow Use shadows for undocked panels or panels w/o dock. (defaults to undefined = don't use)
45 */
46 Ext.ux.InfoPanel = function(el, config, content) {
47
48 config = config || el;
49 // {{{
50 // basic setup
51 var oldHtml = content || null;
52 if(config && config.content) {
53 oldHtml = oldHtml || config.content;
54 delete(config.content);
55 }
56
57 // save autoScroll to this.bodyScroll
58 if(config && config.autoScroll) {
59 this.bodyScroll = config.autoScroll;
60 delete(config.autoScroll);
61 }
62
63 var url;
64 if(el && el.url) {
65 url = el.url;
66 delete(el.url);
67 }
68 if(config && config.url) {
69 url = config.url;
70 delete(config.url);
71 }
72
73 // call parent constructor
74 Ext.ux.InfoPanel.superclass.constructor.call(this, el, config);
75
76 this.desktop = Ext.get(this.desktop) || Ext.get(document.body);
77
78 // shortcut of DomHelper
79 var dh = Ext.DomHelper, oldTitleEl;
80
81 this.el.clean();
82 this.el.addClass(this.panelClass);
83
84 // handle autoCreate
85 if(this.autoCreate) {
86 oldHtml = this.el.dom.innerHTML;
87 this.el.update('');
88 this.desktop.appendChild(this.el);
89 this.el.removeClass('x-layout-inactive-content');
90 }
91 // handle markup
92 else {
93 this.el.clean();
94 if(this.el.dom.firstChild && !this.bodyEl) {
95 this.title = this.title || this.el.dom.firstChild.innerHTML;
96 if(this.el.dom.firstChild.nextSibling) {
97 this.body = Ext.get(this.el.dom.firstChild.nextSibling);
98 }
99 oldTitleEl = this.el.dom.firstChild;
100 oldTitleEl = oldTitleEl.parentNode.removeChild(oldTitleEl);
101 oldTitleEl = null;
102 }
103 }
104
105 // get body element
106 if(this.bodyEl) {
107 this.body = Ext.get(this.bodyEl);
108 this.el.appendChild(this.body);
109 }
110 // }}}
111 // {{{
112 // create title element
113 var create;
114 if('left' === this.buttonPosition ) {
115 create = {
116 tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
117 {tag:'table', cellspacing:0, children: [
118 {tag:'tr', children: [
119 {tag:'td', children:[
120 {tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
121 ]}
122 , {tag:'td', width:'100%', children: [
123 {tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
124 ]}
125 , {tag:'td', cls:'x-dock-panel-title-icon-ct', children: [
126 {tag:'img', alt:'', cls:'x-dock-panel-title-icon'}
127 ]}
128 ]}
129 ]}
130 ]};
131 }
132 else {
133 create = {
134 tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
135 {tag:'table', cellspacing:0, children: [
136 {tag:'tr', children: [
137 {tag:'td', cls:'x-dock-panel-title-icon-ct', children: [
138 {tag:'img', alt:'', cls:'x-dock-panel-title-icon'}
139 ]}
140 , {tag:'td', width:'100%', children: [
141 {tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
142 ]}
143 , {tag:'td', children:[
144 {tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
145 ]}
146 ]}
147 ]}
148 ]};
149 }
150 this.titleEl = dh.insertFirst(this.el.dom, create, true);
151 this.iconImg = this.titleEl.select('img.x-dock-panel-title-icon').item(0);
152 this.titleEl.addClassOnOver('x-dock-panel-title-over');
153 this.titleEl.enableDisplayMode();
154 this.titleTextEl = Ext.get(this.titleEl.select('.x-dock-panel-title-text').elements[0]);
155 this.tools = Ext.get(this.titleEl.select('.x-dock-panel-tools').elements[0]);
156 if('right' === this.titleTextAlign) {
157 this.titleTextEl.addClass('x-dock-panel-title-right');
158 }
159
160 this.tm = Ext.util.TextMetrics.createInstance(this.titleTextEl);
161 // }}}
162 // {{{
163 // set title
164 if(this.title) {
165 this.setTitle(this.title);
166 }
167 // }}}
168 // {{{
169 // create pin button
170 if(this.showPin) {
171 this.stickBtn = this.createTool(this.tools.dom, 'x-layout-stick');
172 this.stickBtn.enableDisplayMode();
173 this.stickBtn.on('click', function(e, target) {
174 e.stopEvent();
175 this.pinned = ! this.pinned;
176 this.updateVisuals();
177 this.fireEvent('pinned', this, this.pinned);
178 }, this);
179 this.stickBtn.hide();
180 }
181 // }}}
182 // {{{
183 // create collapse button
184 if(this.collapsible) {
185 this.collapseBtn = this.createTool(this.tools.dom
186 , (this.collapsed ? 'x-layout-collapse-east' : 'x-layout-collapse-south')
187 );
188 this.collapseBtn.enableDisplayMode();
189 if('title' === this.trigger) {
190 this.titleEl.addClass('x-window-header-text');
191 this.titleEl.on({
192 click:{scope: this, fn:this.toggle}
193 , selectstart:{scope: this, fn: function(e) {
194 e.preventDefault();
195 return false;
196 }}
197 }, this);
198 }
199 else {
200 this.collapseBtn.on("click", this.toggle, this);
201 }
202 }
203 // }}}
204 // {{{
205 // create body if it doesn't exist yet
206 if(!this.body) {
207 this.body = dh.append(this.el, {
208 tag: 'div'
209 , cls: this.bodyClass || null
210 , html: oldHtml || ''
211 }, true);
212 }
213 this.body.enableDisplayMode();
214 if(this.collapsed && !this.pinned) {
215 this.body.hide();
216 }
217 else if(this.pinned) {
218 this.body.show();
219 this.collapsed = false;
220 }
221 this.body.addClass(this.bodyClass);
222 this.body.addClass('x-dock-panel-body-undocked');
223
224 // bodyScroll
225
226 this.scrollEl = this.body;
227
228 // autoScroll -> bodyScroll is experimental due to IE bugs
229 this.scrollEl.setStyle('overflow',
230 this.bodyScroll === true && !this.collapsed ? 'auto' : 'hidden');
231 // }}}
232
233 if(this.fixedHeight) {
234 this.setHeight(this.fixedHeight);
235 }
236
237 if(url) {
238 this.setUrl(url, this.params, this.loadOnce);
239 }
240
241 // install hook for title context menu
242 if(this.titleMenu) {
243 this.setTitleMenu(this.titleMenu);
244 }
245
246 // install hook for icon menu
247 if(this.iconMenu) {
248 this.setIconMenu(this.iconMenu);
249 }
250
251 // {{{
252 // add events
253 this.addEvents({
254 /**
255 * @event beforecollapse
256 * Fires before collapse is taking place. Return false to cancel collapse
257 * @param {Ext.ux.InfoPanel} this
258 */
259 beforecollapse: true
260 /**
261 * @event collapse
262 * Fires after collapse
263 * @param {Ext.ux.InfoPanel} this
264 */
265 , collapse: true
266 /**
267 * @event beforecollapse
268 * Fires before expand is taking place. Return false to cancel expand
269 * @param {Ext.ux.InfoPanel} this
270 */
271 , beforeexpand: true
272 /**
273 * @event expand
274 * Fires after expand
275 * @param {Ext.ux.InfoPanel} this
276 */
277 , expand: true
278 /**
279 * @event pinned
280 * Fires when panel is pinned/unpinned
281 * @param {Ext.ux.InfoPanel} this
282 * @param {Boolean} pinned true if the panel is pinned
283 */
284 , pinned: true
285 /**
286 * @event animationcompleted
287 * Fires when animation is completed
288 * @param {Ext.ux.InfoPanel} this
289 */
290 , animationcompleted: true
291 /**
292 * @event boxchange
293 * Fires when the panel is resized
294 * @param {Ext.ux.InfoPanel} this
295 * @param {Object} box
296 */
297 , boxchange: true
298
299 /**
300 * @event redize
301 * Fires when info panel is resized
302 * @param {Ext.ux.InfoPanel} this
303 * @param {Integer} width New width
304 * @param {Integer} height New height
305 */
306 , resize: true
307
308 /**
309 * @event destroy
310 * Fires after the panel is destroyed
311 * @param {Ext.ux.InfoPanel} this
312 */
313 , destroy: true
314
315 });
316 // }}}
317 // {{{
318 // setup dragging, resizing, and shadow
319 this.setDraggable(this.draggable);
320 this.setResizable(!this.collapsed);
321 this.setShadow(this.useShadow);
322
323 // }}}
324
325 this.el.setStyle('z-index', this.zindex);
326 this.updateVisuals();
327
328 this.id = this.id || this.el.id;
329
330 }; // end of constructor
331
332 // extend
333 Ext.extend(Ext.ux.InfoPanel, Ext.ContentPanel, {
334
335 // {{{
336 // defaults
337 adjustments: [0,0]
338 , collapsible: true
339 , collapsed: true
340 , collapseOnUnpin: true
341 , pinned: false
342 , trigger: 'title'
343 , animate: undefined
344 , duration: 0.35
345 , draggable: undefined
346 , resizable: undefined
347 , docked: false
348 , useShadow: undefined
349 , bodyClass: 'x-dock-panel-body'
350 , panelClass: 'x-dock-panel'
351 , shadowMode: 'sides'
352 , dragPadding: {
353 left:8
354 , right:16
355 , top:0
356 , bottom:8
357 }
358 , lastWidth: 0
359 , lastHeight: 0
360 , minWidth: 0
361 , maxWidth: 9999
362 , minHeight: 50
363 , maxHeight: 9999
364 , autoScroll: false
365 , fixedHeight: undefined
366 , zindex: 10000
367 // }}}
368 // {{{
369 /**
370 * Called internally to create collapse button
371 * Calls utility method of Ext.LayoutRegion createTool
372 * @param {Element/HTMLElement/String} parentEl element to create the tool in
373 * @param {String} className class of the tool
374 */
375 , createTool : function(parentEl, className){
376 return Ext.LayoutRegion.prototype.createTool(parentEl, className);
377 }
378 // }}}
379 // {{{
380 /**
381 * Set title of the InfoPanel
382 * @param {String} title Title to set
383 * @return {Ext.ux.InfoPanel} this
384 */
385 , setTitle: function(title) {
386 this.title = title;
387 this.titleTextEl.update(title);
388 this.setIcon();
389 return this;
390 }
391 // }}}
392 // {{{
393 /**
394 * Set the icon to display in title
395 * @param {String} iconPath path to use for src property of icon img
396 */
397 , setIcon: function(iconPath) {
398 iconPath = iconPath || (this.collapsed ? this.collapsedIcon : this.expandedIcon) || this.icon;
399 if(iconPath) {
400 this.iconImg.dom.src = iconPath;
401 }
402 else {
403 this.iconImg.dom.src = Ext.BLANK_IMAGE_URL;
404 }
405 }
406 // }}}
407 // {{{
408 /**
409 * Assigns menu to title icon
410 * @param {Ext.menu.Menu} menu menu to assign
411 */
412 , setIconMenu: function(menu) {
413 if(this.iconMenu) {
414 this.iconImg.removeAllListeners();
415 }
416 menu.panel = this;
417 this.iconImg.on({
418 click: {
419 scope: this
420 , fn: function(e, target) {
421 e.stopEvent();
422 menu.showAt(e.xy);
423 }}
424 });
425 this.iconMenu = menu;
426 }
427 // }}}
428 // {{{
429 /**
430 * private - title menu click handler
431 * @param {Ext.Event} e event
432 * @param {Element} target target
433 */
434 , onTitleMenu: function(e, target) {
435 e.stopEvent();
436 e.preventDefault();
437 this.titleMenu.showAt(e.xy);
438 }
439 // }}}
440 // {{{
441 /**
442 * Assigns context menu (right click) to the title
443 * @param {Ext.menu.Menu} menu menu to assign
444 */
445 , setTitleMenu: function(menu) {
446 if(this.titleMenu) {
447 this.titleEl.un('contextmenu', this.onTitleMenu, this);
448 }
449 menu.panel = this;
450 this.titleEl.on('contextmenu', this.onTitleMenu, this);
451 this.titleMenu = menu;
452 }
453 // }}}
454 // {{{
455 /**
456 * Get current title
457 * @return {String} Current title
458 */
459 , getTitle: function() {
460 return this.title;
461 }
462 // }}}
463 // {{{
464 /**
465 * Returns body element
466 * This overrides the ContentPanel getEl for convenient access to the body element
467 * @return {Element} this.body
468 */
469 , getEl: function() {
470 return this.body;
471 }
472 // }}}
473 // {{{
474 /**
475 * Returns title height
476 * @return {Integer} title height
477 */
478 , getTitleHeight: function() {
479 return this.titleEl.getComputedHeight();
480 }
481 // }}}
482 // {{{
483 /**
484 * Returns body height
485 * @return {Integer} body height
486 */
487 , getBodyHeight: function() {
488 return this.body.getComputedHeight();
489 }
490 // }}}
491 // {{{
492 /**
493 * Returns panel height
494 * @return {Integer} panel height
495 */
496 , getHeight: function() {
497 return this.getBodyHeight() + this.getTitleHeight();
498 }
499 // }}}
500 // {{{
501 /**
502 * Returns body client height
503 * @return {Integer} body client height
504 */
505 , getBodyClientHeight: function() {
506 return this.body.getHeight(true);
507 }
508 // }}}
509 // {{{
510 /**
511 * Update the innerHTML of this element, optionally searching for and processing scripts
512 * @param {String} html The new HTML
513 * @param {Boolean} loadScripts (optional) true to look for and process scripts
514 * @param {Function} callback For async script loading you can be noticed when the update completes
515 * @return {Ext.Element} this
516 */
517 , update: function(html, loadScripts, callback) {
518 this.body.update(html, loadScripts, callback);
519 return this;
520 }
521 // }}}
522 // {{{
523 /**
524 * Updates this panel's element
525 * @param {String} content The new content
526 * @param {Boolean} loadScripts (optional) true to look for and process scripts
527 */
528 , setContent: function(content, loadScripts) {
529 this.body.update(content, loadScripts);
530 }
531 // }}}
532 // {{{
533 /**
534 * Get the {@link Ext.UpdateManager} for this panel. Enables you to perform Ajax updates.
535 * @return {Ext.UpdateManager} The UpdateManager
536 */
537 , getUpdateManager: function() {
538 return this.body.getUpdateManager();
539 }
540 // }}}
541 // {{{
542 /**
543 * The only required property is url. The optional properties nocache, text and scripts
544 * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this panel UpdateManager instance.
545 * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1&param2=2" or an object {param1: 1, param2: 2}
546 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess, oResponse)
547 * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used url. If true, it will not store the url.
548 * @return {Ext.ContentPanel} this
549 */
550 , load: function() {
551 var um = this.getUpdateManager();
552 um.update.apply(um, arguments);
553 return this;
554 }
555 // }}}
556 // {{{
557 /**
558 * Set a URL to be used to load the content for this panel. When this panel is activated, the content will be loaded from that URL.
559 * @param {String/Function} url The url to load the content from or a function to call to get the url
560 * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Ext.UpdateManager#update} for more details. (Defaults to null)
561 * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
562 * @return {Ext.UpdateManager} The UpdateManager
563 */
564 , setUrl: function(url, params, loadOnce) {
565 if(this.refreshDelegate){
566 this.removeListener("expand", this.refreshDelegate);
567 }
568 this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
569 this.on("expand", this.refreshDelegate);
570 this.on({
571 beforeexpand: {
572 scope: this
573 , single: this.loadOnce ? true : false
574 , fn: function() {
575 this.body.update('');
576 }}
577 });
578 return this.getUpdateManager();
579 }
580 // }}}
581 // {{{
582 , _handleRefresh: function(url, params, loadOnce) {
583 var updater;
584 if(!loadOnce || !this.loaded){
585 updater = this.getUpdateManager();
586 updater.on({
587 update: {
588 scope: this
589 , single: true
590 , fn: function() {
591 if(true === this.useShadow && this.shadow) {
592 this.shadow.show(this.el);
593 }
594 }}
595 });
596 updater.update(url, params, this._setLoaded.createDelegate(this));
597 }
598 }
599 // }}}
600 // {{{
601 , _setLoaded: function() {
602 this.loaded = true;
603 }
604 // }}}
605 // {{{
606 /**
607 * Force a content refresh from the URL specified in the setUrl() method.
608 * Will fail silently if the setUrl method has not been called.
609 * This does not activate the panel, just updates its content.
610 */
611 , refresh: function() {
612 if(this.refreshDelegate){
613 this.loaded = false;
614 this.refreshDelegate();
615 }
616 }
617 // }}}
618 // {{{
619 /**
620 * Expands the panel
621 * @param {Boolean} skipAnimation Set to true to skip animation
622 * @return {Ext.ux.InfoPanel} this
623 */
624 , expand: function(skipAnimation) {
625
626 // do nothing if already expanded
627 if(!this.collapsed) {
628 return this;
629 }
630
631 // fire beforeexpand event
632 if(false === this.fireEvent('beforeexpand', this)) {
633 return this;
634 }
635
636 if(Ext.isGecko) {
637 this.autoScrolls = this.body.select('{overflow=auto}');
638 this.autoScrolls.setStyle('overflow', 'hidden');
639 }
640
641 // reset collapsed flag
642 this.collapsed = false;
643
644 this.autoSize();
645
646 // hide shadow
647 if(!this.docked) {
648 this.setShadow(false);
649 }
650
651 // enable resizing
652 if(this.resizer && !this.docked) {
653 this.setResizable(true);
654 }
655
656 if(Ext.isIE) {
657 this.body.setWidth(this.el.getWidth());
658 }
659
660 // animate expand
661 if(true === this.animate && true !== skipAnimation) {
662 this.body.slideIn('t', {
663 easing: this.easingExpand || null
664 , scope: this
665 , duration: this.duration
666 , callback: this.updateVisuals
667 });
668 }
669
670 // don't animate, just show
671 else {
672 this.body.show();
673 this.updateVisuals();
674 this.fireEvent('animationcompleted', this);
675 }
676
677 // fire expand event
678 this.fireEvent('expand', this);
679
680 return this;
681
682 }
683 // }}}
684 // {{{
685 /**
686 * Toggles the expanded/collapsed states
687 * @param {Boolean} skipAnimation Set to true to skip animation
688 * @return {Ext.ux.InfoPanel} this
689 */
690 , toggle: function(skipAnimation) {
691 if(this.collapsed) {
692 this.expand(skipAnimation);
693 }
694 else {
695 this.collapse(skipAnimation);
696 }
697 return this;
698 }
699 // }}}
700 // {{{
701 /**
702 * Collapses the panel
703 * @param {Boolean} skipAnimation Set to true to skip animation
704 * @return {Ext.ux.InfoPanel} this
705 */
706 , collapse: function(skipAnimation) {
707
708 // do nothing if already collapsed or pinned
709 if(this.collapsed || this.pinned) {
710 return this;
711 }
712
713 // fire beforecollapse event
714 if(false === this.fireEvent('beforecollapse', this)) {
715 return this;
716 }
717
718 if(Ext.isGecko) {
719 this.autoScrolls = this.body.select('{overflow=auto}');
720 this.autoScrolls.setStyle('overflow', 'hidden');
721 }
722
723 if(this.bodyScroll /*&& !Ext.isIE*/) {
724 this.scrollEl.setStyle('overflow','hidden');
725 }
726
727 // set collapsed flag
728 this.collapsed = true;
729
730 // hide shadow
731 this.setShadow(false);
732
733 // disable resizing of collapsed panel
734 if(this.resizer) {
735 this.setResizable(false);
736 }
737
738 // animate collapse
739 if(true === this.animate && true !== skipAnimation) {
740 this.body.slideOut('t', {
741 easing: this.easingCollapse || null
742 , scope: this
743 , duration: this.duration
744 , callback: this.updateVisuals
745 });
746 }
747
748 // don't animate, just hide
749 else {
750 this.body.hide();
751 this.updateVisuals();
752 this.fireEvent('animationcompleted', this);
753 }
754
755 // fire collapse event
756 this.fireEvent('collapse', this);
757
758 return this;
759
760 }
761 // }}}
762 // {{{
763 /**
764 * Called internally to update class of the collapse button
765 * as part of expand and collapse methods
766 *
767 * @return {Ext.ux.InfoPanel} this
768 */
769 , updateVisuals: function() {
770
771 // handle collapsed state
772 if(this.collapsed) {
773 if(this.showPin) {
774 if(this.collapseBtn) {
775 this.collapseBtn.show();
776 }
777 if(this.stickBtn) {
778 this.stickBtn.hide();
779 }
780 }
781 if(this.collapseBtn) {
782 Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
783 'x-layout-collapse-south', 'x-layout-collapse-east'
784 );
785 }
786 this.body.replaceClass('x-dock-panel-body-expanded', 'x-dock-panel-body-collapsed');
787 this.titleEl.replaceClass('x-dock-panel-title-expanded', 'x-dock-panel-title-collapsed');
788 }
789
790 // handle expanded state
791 else {
792 if(this.showPin) {
793 if(this.pinned) {
794 if(this.stickBtn) {
795 Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stick', 'x-layout-stuck');
796 }
797 this.titleEl.addClass('x-dock-panel-title-pinned');
798 }
799 else {
800 if(this.stickBtn) {
801 Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stuck', 'x-layout-stick');
802 }
803 this.titleEl.removeClass('x-dock-panel-title-pinned');
804 }
805 if(this.collapseBtn) {
806 this.collapseBtn.hide();
807 }
808 if(this.stickBtn) {
809 this.stickBtn.show();
810 }
811 }
812 else {
813 if(this.collapseBtn) {
814 Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
815 'x-layout-collapse-east', 'x-layout-collapse-south'
816 );
817 }
818 }
819 this.body.replaceClass('x-dock-panel-body-collapsed', 'x-dock-panel-body-expanded');
820 this.titleEl.replaceClass('x-dock-panel-title-collapsed', 'x-dock-panel-title-expanded');
821 }
822
823 // show shadow if necessary
824 if(!this.docked) {
825 this.setShadow(true);
826 }
827
828 if(this.autoScrolls) {
829 this.autoScrolls.setStyle('overflow', 'auto');
830 }
831
832 this.setIcon();
833
834 if(this.bodyScroll && !this.docked && !this.collapsed /*&& !Ext.isIE*/) {
835 this.scrollEl.setStyle('overflow', 'auto');
836 }
837
838 this.constrainToDesktop();
839
840 // fire animationcompleted event
841 this.fireEvent('animationcompleted', this);
842
843 // clear visibility style of body's children
844 var kids = this.body.select('div[className!=x-grid-viewport],input{visibility}');
845 kids.setStyle.defer(1, kids, ['visibility','']);
846
847 // restore body overflow
848 if(this.bodyScroll && !this.collapsed /*&& !Ext.isIE*/) {
849 this.setHeight(this.getHeight());
850 this.scrollEl.setStyle('overflow','auto');
851 }
852
853 return this;
854 }
855 // }}}
856 // {{{
857 /**
858 * Creates toolbar
859 * @param {Array} config Configuration for Ext.Toolbar
860 * @param {Boolean} bottom true to create bottom toolbar. (defaults to false = top toolbar)
861 * @return {Ext.Toolbar} Ext.Toolbar object
862 */
863 , createToolbar: function(config, bottom) {
864
865 // we need clean body
866 this.body.clean();
867
868 // copy body to new container
869 this.scrollEl = Ext.DomHelper.append(document.body, {tag:'div'}, true);
870 var el;
871 while(el = this.body.down('*')) {
872 this.scrollEl.appendChild(el);
873 }
874
875 if(this.bodyScroll) {
876 this.body.setStyle('overflow', '');
877 if(!this.collapsed) {
878 this.scrollEl.setStyle('overflow', 'auto');
879 }
880 }
881
882 var create = {tag:'div'}, tbEl;
883 config =