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 = config || null;
  884     if(bottom) {
  885       this.body.appendChild(this.scrollEl);
  886       tbEl = Ext.DomHelper.append(this.body, create, true);
  887       tbEl.addClass('x-dock-panel-toolbar-bottom');
  888     }
  889     else {
  890       tbEl = Ext.DomHelper.insertFirst(this.body, create, true);
  891       tbEl.addClass('x-dock-panel-toolbar');
  892       this.body.appendChild(this.scrollEl);
  893     }
  894     this.toolbar = new Ext.Toolbar(tbEl, config);
  895     this.setHeight(this.getHeight());
  896     return this.toolbar;
  897   }
  898   // }}}
  899   // {{{
  900   /**
  901     * Set the panel draggable
  902     * Uses lazy creation of dd object
  903     * @param {Boolean} enable pass false to disable dragging
  904     * @return {Ext.ux.InfoPanel} this
  905     */
  906   , setDraggable: function(enable) {
  907 
  908     if(true !== this.draggable) {
  909       return this;
  910     }
  911 
  912     // lazy create proxy
  913     var dragTitleEl;
  914     if(!this.proxy) {
  915       this.proxy = this.el.createProxy('x-dlg-proxy');
  916 
  917       // setup title
  918       dragTitleEl = Ext.DomHelper.append(this.proxy, {tag:'div'}, true);
  919       dragTitleEl.update(this.el.dom.firstChild.innerHTML);
  920       dragTitleEl.dom.className = this.el.dom.firstChild.className;
  921       if(this.collapsed && Ext.isIE) {
  922         dragTitleEl.dom.style.borderBottom = "0";
  923       }
  924 
  925       this.proxy.hide();
  926       this.proxy.setOpacity(0.5);
  927       this.dd = new Ext.dd.DDProxy(this.el.dom, 'PanelDrag', {
  928         dragElId: this.proxy.id
  929         , scroll: false
  930       });
  931       this.dd.scroll = false;
  932       this.dd.afterDrag = function() {
  933         this.panel.moveToViewport();
  934         if(this.panel && this.panel.shadow && !this.panel.docked) {
  935           this.panel.shadow.show(this.panel.el);
  936         }
  937       };
  938 
  939       this.constrainToDesktop();
  940       Ext.EventManager.onWindowResize(this.moveToViewport, this);
  941     }
  942 
  943     this.dd.panel = this;
  944     this.dd.setHandleElId(this.titleEl.id);
  945     if(false === enable) {
  946       this.dd.lock();
  947     }
  948     else {
  949       this.dd.unlock();
  950     }
  951 
  952     return this;
  953   }
  954   // }}}
  955   // {{{
  956   /**
  957     * Set the panel resizable
  958     * Uses lazy creation of the resizer object
  959     * @param {Boolean} pass false to disable resizing
  960     * @return {Ext.ux.InfoPanel} this
  961     */
  962   , setResizable: function(enable) {
  963 
  964     if(true !== this.resizable) {
  965       return this;
  966     }
  967 
  968     // {{{
  969     // lazy create resizer
  970     if(!this.resizer) {
  971 
  972       // {{{
  973       // create resizer
  974       this.resizer = new Ext.Resizable(this.el, {
  975         handles: 's w e sw se'
  976         , minWidth: this.minWidth || this.tm.getWidth(this.getTitle()) + 56 || 48
  977         , maxWidth: this.maxWidth
  978         , minHeight: this.minHeight
  979         , maxHeight: this.maxHeight
  980         , transparent: true
  981         , draggable: false
  982       });
  983       // }}}
  984       // {{{
  985       // install event handlers
  986       this.resizer.on({
  987         beforeresize: {
  988           scope:this
  989           , fn: function(resizer, e) {
  990             var viewport = this.getViewport();
  991             var box = this.getBox();
  992 
  993             var pos = resizer.activeHandle.position;
  994 
  995             // left constraint
  996             if(pos.match(/west/)) {
  997               resizer.minX = viewport.x + (this.dragPadding.left || 8);
  998             }
  999 
 1000             // down constraint
 1001             var maxH;
 1002             if(pos.match(/south/)) {
 1003               resizer.oldMaxHeight = resizer.maxHeight;
 1004               maxH = viewport.y + viewport.height - box.y - (this.dragPadding.bottom || 8);
 1005               resizer.maxHeight = maxH < resizer.maxHeight ? maxH : resizer.maxHeight;
 1006             }
 1007 
 1008             // right constraint
 1009             var maxW;
 1010             if(pos.match(/east/)) {
 1011               resizer.oldMaxWidth = resizer.maxWidth;
 1012               maxW = viewport.x + viewport.width - box.x - (this.dragPadding.right || 10);
 1013               resizer.maxWidth = maxW < resizer.maxWidth ? maxW : resizer.maxWidth;
 1014             }
 1015         }}
 1016         , resize: {
 1017           scope: this
 1018           , fn: function(resizer, width, height, e) {
 1019             resizer.maxHeight = resizer.oldMaxHeight || resizer.maxHeight;
 1020             resizer.maxWidth = resizer.oldMaxWidth || resizer.maxWidth;
 1021             this.setSize(width, height);
 1022             this.constrainToDesktop();
 1023             this.fireEvent('boxchange', this, this.el.getBox());
 1024             this.fireEvent('resize', this, width, height);
 1025             this.lastHeight = height;
 1026             this.lastWidth = width;
 1027         }}
 1028       });
 1029       // }}}
 1030 
 1031     }
 1032     // }}}
 1033 
 1034     this.resizer.enabled = enable;
 1035 
 1036     // this is custom override of Ext.Resizer
 1037     this.resizer.showHandles(enable);
 1038 
 1039     return this;
 1040   }
 1041   // }}}
 1042   // {{{
 1043   /**
 1044     * Called internally to clip passed width and height to viewport
 1045     * @param {Integer} w width
 1046     * @param {Integer} h height
 1047     * @return {Object} {width:safeWidth, height:safeHeight}
 1048     */
 1049   , safeSize: function(w, h) {
 1050     var viewport = this.getViewport();
 1051     var box = this.getBox();
 1052     var gap = 0;
 1053     var safeSize = {width:w, height:h};
 1054 
 1055     safeSize.height =
 1056       box.y + h + this.dragPadding.bottom + gap > viewport.height + viewport.y
 1057       ? viewport.height - box.y + viewport.y - this.dragPadding.bottom - gap
 1058       : safeSize.height
 1059     ;
 1060 
 1061     safeSize.width =
 1062       box.x + w + this.dragPadding.right + gap > viewport.width + viewport.x
 1063       ? viewport.width - box.x + viewport.x - this.dragPadding.right - gap
 1064       : safeSize.width
 1065     ;
 1066 
 1067     return safeSize;
 1068   }
 1069   // }}}
 1070   // {{{
 1071   /**
 1072     * Called internally to get current viewport
 1073     * @param {Element/HTMLElement/String} desktop Element to get size and position of
 1074     * @return {Object} viewport {x:x, y:y, width:width, height:height} x and y are page coords
 1075     */
 1076   , getViewport: function(desktop) {
 1077 
 1078     desktop = desktop || this.desktop || document.body;
 1079     var viewport = Ext.get(desktop).getViewSize();
 1080     var xy;
 1081     if(document.body === desktop.dom) {
 1082       viewport.x = 0;
 1083       viewport.y = 0;
 1084     }
 1085     else {
 1086       xy = desktop.getXY();
 1087       viewport.x = isNaN(xy[0]) ? 0 : xy[0];
 1088       viewport.y = isNaN(xy[1]) ? 0 : xy[1];
 1089     }
 1090 
 1091     return viewport;
 1092   }
 1093   // }}}
 1094   // {{{
 1095   /**
 1096     * Sets the size of the panel. Demanded size is clipped to the viewport
 1097     *
 1098     * @param {Integer} w width to set
 1099     * @param {Integer} h height to set
 1100     * @return {Ext.ux.InfoPanel} this
 1101     */
 1102   , setSize: function(w, h) {
 1103     var safeSize = this.safeSize(w, h);
 1104     this.setWidth(safeSize.width);
 1105     this.setHeight(safeSize.height);
 1106     if(Ext.isIE) {
 1107       this.body.setWidth(safeSize.width);
 1108     }
 1109 
 1110     if(!this.docked) {
 1111       this.setShadow(true);
 1112     }
 1113   }
 1114   // }}}
 1115   // {{{
 1116   /**
 1117     * Sets the width of the panel. Demanded width is clipped to the viewport
 1118     *
 1119     * @param {Integer} w width to set
 1120     * @return {Ext.ux.InfoPanel} this
 1121     */
 1122   , setWidth: function(w) {
 1123     this.el.setWidth(w);
 1124     this.body.setStyle('width','');
 1125     if(!this.docked) {
 1126       this.setShadow(true);
 1127     }
 1128     this.lastWidth = w;
 1129 
 1130     return this;
 1131   }
 1132   // }}}
 1133   // {{{
 1134   /**
 1135     * Sets the height of the panel. Demanded height is clipped to the viewport
 1136     *
 1137     * @param {Integer} h height to set
 1138     * @return {Ext.ux.InfoPanel} this
 1139     */
 1140   , setHeight: function(h) {
 1141     var newH = h - this.getTitleHeight();
 1142     var scrollH = newH;
 1143     if(1 < newH) {
 1144       if(this.scrollEl !== this.body) {
 1145         scrollH -= this.toolbar ? this.toolbar.getEl().getHeight() : 0;
 1146 //        scrollH -= 27;
 1147         scrollH -= this.adjustments[1] || 0;
 1148         this.scrollEl.setHeight(scrollH);
 1149       }
 1150       this.body.setHeight(newH);
 1151     }
 1152     else {
 1153       this.body.setStyle('height','');
 1154     }
 1155 
 1156     if(!this.docked) {
 1157       this.setShadow(true);
 1158     }
 1159 //    this.lastHeight = h;
 1160     this.el.setStyle('height','');
 1161 
 1162     return this;
 1163   }
 1164   // }}}
 1165   // {{{
 1166   /**
 1167     * Called internally to set x, y, width and height of the panel
 1168     *
 1169     * @param {Object} box
 1170     * @return {Ext.ux.InfoPanel} this
 1171     */
 1172   , setBox: function(box) {
 1173     this.el.setBox(box);
 1174     this.moveToViewport();
 1175     this.setSize(box.width, box.height);
 1176 
 1177     return this;
 1178   }
 1179   // }}}
 1180   // {{{
 1181   /**
 1182     * Called internally to get the box of the panel
 1183     *
 1184     * @return {Object} box
 1185     */
 1186   , getBox: function() {
 1187     return this.el.getBox();
 1188   }
 1189   // }}}
 1190   // {{{
 1191   , autoSize: function() {
 1192 
 1193     var width = 0;
 1194     var height = this.fixedHeight || 0;
 1195     var dock = this.dock;
 1196 
 1197     // docked
 1198     if(this.docked && this.dock) {
 1199       if(dock.fitHeight) {
 1200         height = dock.getPanelBodyHeight() + this.getTitleHeight();
 1201       }
 1202     }
 1203 
 1204     // undocked
 1205     else {
 1206       // height logic
 1207       height = this.lastHeight || this.fixedHeight || 0;
 1208       height = height < this.maxHeight ? height : (this.maxHeight < 9999 ? this.maxHeight : 0);
 1209       height = (height && height < this.minHeight ) ? this.minHeight : height;
 1210       this.lastHeight = height ? height : this.lastHeight;
 1211     }
 1212 
 1213     this.setHeight(height);
 1214 
 1215   }
 1216   // }}}
 1217   // {{{
 1218   /**
 1219     * Turns shadow on/off
 1220     * Uses lazy creation of the shadow object
 1221     * @param {Boolean} shadow pass false to hide, true to show the shadow
 1222     * @return {Ext.ux.InfoPanel} this
 1223     */
 1224   , setShadow: function(shadow) {
 1225 
 1226     // if I have shadow but shouldn't use it
 1227     if(this.shadow && true !== this.useShadow) {
 1228       this.shadow.hide();
 1229       return this;
 1230     }
 1231 
 1232     // if I shouldn't use shadow
 1233     if(true !== this.useShadow) {
 1234       return this;
 1235     }
 1236 
 1237     // if I don't have shadow
 1238     if(!this.shadow) {
 1239       this.shadow = new Ext.Shadow({mode:this.shadowMode});
 1240     }
 1241 
 1242     // show or hide
 1243     var zindex;
 1244     if(shadow) {
 1245       this.shadow.show(this.el);
 1246 
 1247       // fix the Ext shadow z-index bug
 1248       zindex = parseInt(this.el.getStyle('z-index'), 10);
 1249       zindex = isNaN(zindex) ? '' : zindex - 1;
 1250       this.shadow.el.setStyle('z-index', zindex);
 1251     }
 1252     else {
 1253       this.shadow.hide();
 1254     }
 1255 
 1256     return this;
 1257 
 1258   }
 1259   // }}}
 1260   // {{{
 1261   /**
 1262     * Show the panel
 1263     * @param {Boolean} show (optional) if false hides the panel instead of show
 1264     * @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
 1265     * @return {Ext.ux.InfoPanel} this
 1266     */
 1267   , show: function(show, alsoUndocked) {
 1268 
 1269     // ignore undocked panels if not forced to
 1270     if(!this.docked && true !== alsoUndocked) {
 1271       return this;
 1272     }
 1273 
 1274     show = (false === show ? false : true);
 1275     if(!this.docked) {
 1276       this.setShadow(show);
 1277     }
 1278 
 1279     this.el.setStyle('display', show ? '' : 'none');
 1280     return this;
 1281   }
 1282   // }}}
 1283   // {{{
 1284   /**
 1285     * Hide the panel
 1286     * @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
 1287     * @return {Ext.ux.InfoPanel} this
 1288     */
 1289   , hide: function(alsoUndocked) {
 1290     this.show(false, alsoUndocked);
 1291   }
 1292   // }}}
 1293   // {{{
 1294   /**
 1295     * Constrains dragging of this panel to desktop boundaries
 1296     * @param {Element} desktop the panel is to be constrained to
 1297     * @return {Ext.ux.InfoPanel} this
 1298     */
 1299   , constrainToDesktop: function(desktop) {
 1300     desktop = desktop || this.desktop;
 1301     if(desktop && this.dd) {
 1302       this.dd.constrainTo(desktop, this.dragPadding, false);
 1303     }
 1304     return this;
 1305   }
 1306   // }}}
 1307   // {{{
 1308   /**
 1309     * Called internally to move the panel to the viewport.
 1310     * Also constrains the dragging to the desktop
 1311     *
 1312     * @param {Object} viewport (optional) object {x:x, y:y, width:width, height:height}
 1313     * @return {Ext.ux.InfoPanel} this
 1314     */
 1315   , moveToViewport: function(viewport) {
 1316     viewport = viewport && !isNaN(viewport.x) ? viewport : this.getViewport();
 1317     var box = this.getBox();
 1318     var moved = false;
 1319     var gap = 10;
 1320 
 1321     // horizontal
 1322     if(box.x + box.width + this.dragPadding.right > viewport.x + viewport.width) {
 1323       moved = true;
 1324       box.x = viewport.width + viewport.x - box.width - this.dragPadding.right - gap;
 1325     }
 1326     if(box.x - this.dragPadding.left < viewport.x) {
 1327       moved = true;
 1328       box.x = viewport.x + this.dragPadding.left + gap;
 1329     }
 1330 
 1331     // vertical
 1332     if(box.y + box.height + this.dragPadding.bottom > viewport.y + viewport.height) {
 1333       moved = true;
 1334       box.y = viewport.height + viewport.y - box.height - this.dragPadding.bottom - gap;
 1335     }
 1336     if(box.y - this.dragPadding.top < viewport.y) {
 1337       moved = true;
 1338       box.y = viewport.y + this.dragPadding.top + gap;
 1339     }
 1340 
 1341     var oldOverflow;
 1342     if(moved) {
 1343       // sanity clip
 1344       box.x = box.x < viewport.x ? viewport.x : box.x;
 1345       box.y = box.y < viewport.y ? viewport.y : box.y;
 1346 
 1347       // prevent scrollbars from appearing
 1348       this.desktop.oldOverflow = this.desktop.oldOverflow || this.desktop.getStyle('overflow');
 1349       this.desktop.setStyle('overflow', 'hidden');
 1350 
 1351       // set position
 1352       this.el.setXY([box.x, box.y]);
 1353 
 1354       // restore overflow
 1355       this.desktop.setStyle.defer(100, this.desktop, ['overflow', this.desktop.oldOverflow]);
 1356 
 1357       if(!this.docked) {
 1358         this.setShadow(true);
 1359       }
 1360     }
 1361 
 1362     this.constrainToDesktop();
 1363 
 1364     return this;
 1365   }
 1366   // }}}
 1367   // {{{
 1368   /**
 1369     * destroys the panel
 1370     */
 1371   , destroy: function() {
 1372     if(this.shadow) {
 1373       this.shadow.hide();
 1374     }
 1375     if(this.collapsible) {
 1376       this.collapseBtn.removeAllListeners();
 1377       this.titleEl.removeAllListeners();
 1378     }
 1379 
 1380     if(this.resizer) {
 1381       this.resizer.destroy();
 1382     }
 1383     if(this.dd) {
 1384       if(this.proxy) {
 1385         this.proxy.removeAllListeners();
 1386         this.proxy.remove();
 1387       }
 1388       this.dd.unreg();
 1389       this.dd = null;
 1390     }
 1391     if(this.dock) {
 1392       this.dock.detach(this);
 1393     }
 1394 
 1395     this.body.removeAllListeners();
 1396 
 1397     // call parent destroy
 1398     Ext.ux.InfoPanel.superclass.destroy.call(this);
 1399 
 1400     this.fireEvent('destroy', this);
 1401 
 1402   }
 1403   // }}}
 1404 
 1405 }); // end of extend
 1406 
 1407 // {{{
 1408 // show/hide resizer handles override
 1409 Ext.override(Ext.Resizable, {
 1410 
 1411   /**
 1412     * Hide resizer handles
 1413     */
 1414   hideHandles: function() {
 1415     this.showHandles(false);
 1416   } // end of function hideHandles
 1417 
 1418   /**
 1419     * Show resizer handles
 1420     *
 1421     * @param {Boolean} show (true = show, false = hide)
 1422     */
 1423   , showHandles: function(show) {
 1424     show = (false === show ? false : true);
 1425     var pos;
 1426     for(var p in Ext.Resizable.positions) {
 1427       pos = Ext.Resizable.positions[p];
 1428       if(this[pos]) {
 1429         this[pos].el.setStyle('display', show ? '' : 'none');
 1430       }
 1431     }
 1432   } // end of function showHandles
 1433 // }}}
 1434 
 1435 });
 1436 
 1437 // end of file