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 =