Ext.ux.InfoPanel extension class code:
    1 // vim: ts=2:sw=2: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 18 2007-06-13 17:55:16Z 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 false to switch animation of expand/collapse off (defaults to true)
   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 {Boolean} collapsed false to start with the expanded body (defaults to true)
   23   * @cfg {Boolean} collapseOnUnpin unpinned panel is collapsed when possible (defaults to true)
   24   * @cfg {Boolean} collapsible false to disable collapsibility (defaults to true)
   25   * @cfg {Boolean} draggable true to allow panel dragging (defaults to false)
   26   * @cfg {Float} duration Duration of animation in seconds (defaults to 0.35)
   27   * @cfg {String} easingCollapse Easing to use for collapse animation (e.g. 'backIn')
   28   * @cfg {String} easingExpand Easing to use for expand animation (e.g. 'backOut')
   29   * @cfg {String} icon Path for icon to display in the title
   30   * @cfg {Integer} minWidth minimal width in pixels of the resizable panel (no default)
   31   * @cfg {Integer} maxWidth maximal width in pixels of the resizable panel (no default)
   32   * @cfg {Integer} minHeight minimal height in pixels of the resizable panel (no default)
   33   * @cfg {Integer} maxHeight maximal height in pixels of the resizable panel (no default)
   34   * @cfg {String} panelClass Set to override the default 'x-dock-panel' class.
   35   * @cfg {Boolean} pinned true to start in pinned state (implies collapsed:false) (defaults to false)
   36   * @cfg {Boolean} resizable true to allow use resize width of the panel.
   37   *  Handles are transparent. (defaults to false)
   38   * @cfg {String} shadowMode defaults to 'sides'.
   39   * @cfg {Boolean} showPin Show the pin button - makes sense only if panel is part of Accordion
   40   * @cfg {String} trigger 'title' or 'button'. Click where expands/collapses the panel (defaults to 'title')
   41   * @cfg {Boolean} useShadow Use shadows for undocked panels or panels w/o dock. (defaults to true)
   42   */
   43 Ext.ux.InfoPanel = function(el, config, content) {
   44 
   45   // {{{
   46   // basic setup
   47   var oldHtml = content || null;
   48   if(config && config.content) {
   49     oldHtml = oldHtml || config.content;
   50     delete(config.content);
   51   }
   52 
   53   // save autoScroll to this.bodyScroll
   54   if(config && config.autoScroll) {
   55     this.bodyScroll = config.autoScroll;
   56     delete(config.autoScroll);
   57   }
   58 
   59   // call parent constructor
   60   Ext.ux.InfoPanel.superclass.constructor.call(this, el, config);
   61 
   62   this.desktop = Ext.get(this.desktop) || Ext.get(document.body);
   63 
   64   // shortcut of DomHelper
   65   var dh = Ext.DomHelper, oldTitleEl;
   66 
   67   this.el.clean();
   68   this.el.addClass(this.panelClass);
   69 
   70   // handle autoCreate
   71   if(this.autoCreate) {
   72     oldHtml = this.el.dom.innerHTML;
   73     this.el.update('');
   74     this.desktop.appendChild(this.el);
   75     this.el.removeClass('x-layout-inactive-content');
   76   }
   77   // handle markup
   78   else {
   79     this.el.clean();
   80     if(this.el.dom.firstChild && !this.bodyEl) {
   81       this.title = this.title || this.el.dom.firstChild.innerHTML;
   82       if(this.el.dom.firstChild.nextSibling) {
   83         this.body = Ext.get(this.el.dom.firstChild.nextSibling);
   84       }
   85       oldTitleEl = this.el.dom.firstChild;
   86       oldTitleEl = oldTitleEl.parentNode.removeChild(oldTitleEl);
   87       oldTitleEl = null;
   88     }
   89   }
   90 
   91   // get body element
   92   if(this.bodyEl) {
   93     this.body = Ext.get(this.bodyEl);
   94     this.el.appendChild(this.body);
   95   }
   96   // }}}
   97   // {{{
   98   // create title element
   99   var create = {
  100     tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
  101       {tag:'table', cellspacing:0, children: [
  102         {tag:'tr', children: [
  103           {tag:'td', width:'100%', children: [
  104             {tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
  105           ]}
  106           , {tag:'td', children:[
  107             {tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
  108           ]}
  109         ]}
  110       ]}
  111     ]};
  112   this.titleEl = dh.insertFirst(this.el.dom, create, true);
  113   this.titleEl.addClassOnOver('x-dock-panel-title-over');
  114   this.titleEl.enableDisplayMode();
  115   this.titleTextEl = Ext.get(this.titleEl.select('.x-dock-panel-title-text').elements[0]);
  116   this.tools = Ext.get(this.titleEl.select('.x-dock-panel-tools').elements[0]);
  117 
  118   this.tm = Ext.util.TextMetrics.createInstance(this.titleTextEl);
  119   // }}}
  120   // {{{
  121   // set title
  122   if(this.title) {
  123     this.setTitle(this.title);
  124   }
  125   // }}}
  126   // {{{
  127   // create pin button
  128   if(this.showPin) {
  129     this.stickBtn = this.createTool(this.tools.dom, 'x-layout-stick');
  130     this.stickBtn.enableDisplayMode();
  131     this.stickBtn.on('click', function(e, target) {
  132       e.stopEvent();
  133       this.pinned = ! this.pinned;
  134       this.updateVisuals();
  135       this.fireEvent('pinned', this, this.pinned);
  136     }, this);
  137     this.stickBtn.hide();
  138   }
  139   // }}}
  140   // {{{
  141   // create collapse button
  142   if(this.collapsible) {
  143     this.collapseBtn = this.createTool(this.tools.dom
  144       , (this.collapsed ? 'x-layout-collapse-east' : 'x-layout-collapse-south')
  145     );
  146     this.collapseBtn.enableDisplayMode();
  147     if('title' === this.trigger) {
  148       this.titleEl.addClass('x-window-header-text');
  149       this.titleEl.on({
  150           click:{scope: this, fn:this.toggle}
  151         , selectstart:{scope: this, fn: function(e) {
  152             e.preventDefault();
  153             return false;
  154         }}
  155       }, this);
  156     }
  157     else {
  158       this.collapseBtn.on("click", this.toggle, this);
  159     }
  160   }
  161   // }}}
  162   // {{{
  163   // create body if it doesn't exist yet
  164   if(!this.body) {
  165       this.body = dh.append(this.el, {
  166         tag: 'div'
  167         , cls: this.bodyClass || null
  168         , html: oldHtml || ''
  169         }, true);
  170   }
  171   this.body.enableDisplayMode();
  172   if(this.collapsed && !this.pinned) {
  173     this.body.hide();
  174   }
  175   else if(this.pinned) {
  176     this.body.show();
  177     this.collapsed = false;
  178     this.updateVisuals();
  179   }
  180   this.body.addClass(this.bodyClass);
  181   this.body.addClass('x-dock-panel-body-undocked');
  182 
  183   // bodyScroll
  184   // autoScroll -> bodyScroll is experimental due to IE bugs
  185   if(!Ext.isIE) {
  186     this.body.setStyle('overflow', this.bodyScroll === true ? 'auto' : 'hidden');
  187   }
  188   // }}}
  189   // {{{
  190   // add events
  191   this.addEvents({
  192     /**
  193       * @event beforecollapse
  194       * Fires before collapse is taking place. Return false to cancel collapse
  195       * @param {Ext.ux.InfoPanel} this
  196       */
  197     beforecollapse: true
  198     /**
  199       * @event collapse
  200       * Fires after collapse
  201       * @param {Ext.ux.InfoPanel} this
  202       */
  203     , collapse: true
  204     /**
  205       * @event beforecollapse
  206       * Fires before expand is taking place. Return false to cancel expand
  207       * @param {Ext.ux.InfoPanel} this
  208       */
  209     , beforeexpand: true
  210     /**
  211       * @event expand
  212       * Fires after expand
  213       * @param {Ext.ux.InfoPanel} this
  214       */
  215     , expand: true
  216     /**
  217       * @event pinned
  218       * Fires when panel is pinned/unpinned
  219       * @param {Ext.ux.InfoPanel} this
  220       * @param {Boolean} pinned true if the panel is pinned
  221       */
  222     , pinned: true
  223     /**
  224       * @event animationcompleted
  225       * Fires when animation is completed
  226       * @param {Ext.ux.InfoPanel} this
  227       */
  228     , animationcompleted: true
  229     /**
  230       * @event boxchange
  231       * Fires when the panel is resized
  232       * @param {Ext.ux.InfoPanel} this
  233       * @param {Object} box
  234       */
  235     , boxchange: true
  236 
  237     /**
  238       * @event redize
  239       * Fires when info panel is resized
  240       * @param {Ext.ux.InfoPanel} this
  241       * @param {Integer} width New width
  242       * @param {Integer} height New height
  243       */
  244     , resize: true
  245 
  246     /**
  247       * @event destroy
  248       * Fires after the panel is destroyed
  249       * @param {Ext.ux.InfoPanel} this
  250       */
  251     , destroy: true
  252 
  253   });
  254   // }}}
  255   // {{{
  256   // setup dragging, resizing, and shadow
  257   this.setDraggable(this.draggable);
  258   this.setResizable(!this.collapsed);
  259   this.setShadow(this.useShadow);
  260 
  261   /*
  262   // todo revise
  263   if(this.minHeight && !this.docked) {
  264     this.setHeight(this.minHeight);
  265   }
  266 
  267   if(this.minWidth && !this.docked) {
  268     this.setWidth(this.minWidth);
  269   }
  270   */
  271   // }}}
  272 
  273   this.id = this.el.id;
  274 
  275 }; // end of constructor
  276 
  277 // extend
  278 Ext.extend(Ext.ux.InfoPanel, Ext.ContentPanel, {
  279 
  280   // {{{
  281   // defaults
  282   collapsible: true
  283   , collapsed: true
  284   , collapseOnUnpin: true
  285   , pinned: false
  286   , trigger: 'title'
  287   , animate: true
  288   , duration: 0.35
  289   , draggable: false
  290   , resizable: false
  291   , docked: false
  292   , useShadow: false
  293   , bodyClass: 'x-dock-panel-body'
  294   , panelClass: 'x-dock-panel'
  295   , shadowMode: 'sides'
  296   , dragPadding: {
  297       left:8
  298     , right:16
  299     , top:0
  300     , bottom:8
  301   }
  302   , lastWidth: 0
  303   , lastHeight: 0
  304   , autoScroll: false
  305   // }}}
  306   // {{{
  307   /**
  308     * Called internally to create collapse button
  309     * Calls utility method of Ext.LayoutRegion createTool
  310     * @param {Element/HTMLElement/String} parentEl element to create the tool in
  311     * @param {String} className class of the tool
  312     */
  313   , createTool : function(parentEl, className){
  314     return Ext.LayoutRegion.prototype.createTool(parentEl, className);
  315   }
  316   // }}}
  317   // {{{
  318   /**
  319     * Set title of the InfoPanel
  320     * @param {String} title Title to set
  321     * @return {Ext.ux.InfoPanel} this
  322     */
  323   , setTitle: function(title) {
  324     this.title = title;
  325     this.titleTextEl.update(title);
  326     if(this.icon) {
  327       this.titleTextEl.set({
  328         style:
  329           'background-image:url('
  330           + this.icon
  331           + ');background-repeat:no-repeat;background-position:3px 50%;padding-left:23px;'
  332       });
  333     }
  334     return this;
  335   }
  336   // }}}
  337   // {{{
  338   /**
  339     * Get current title
  340     * @return {String} Current title
  341     */
  342   , getTitle: function() {
  343     return this.title;
  344   }
  345   // }}}
  346   // {{{
  347   /**
  348     * Returns body element
  349     * This overrides the ContentPanel getEl for convenient access to the body element
  350     * @return {Element} this.body
  351     */
  352   , getEl: function() {
  353     return this.body;
  354   }
  355   // }}}
  356   // {{{
  357   /**
  358     * Update the innerHTML of this element, optionally searching for and processing scripts
  359     * @param {String} html The new HTML
  360     * @param {Boolean} loadScripts (optional) true to look for and process scripts
  361     * @param {Function} callback For async script loading you can be noticed when the update completes
  362     * @return {Ext.Element} this
  363     */
  364   , update: function(html, loadScripts, callback) {
  365     this.body.update(html, loadScripts, callback);
  366     return this;
  367   }
  368   // }}}
  369   // {{{
  370   /**
  371     * Expands the panel
  372     * @return {Ext.ux.InfoPanel} this
  373     */
  374   , expand: function() {
  375 
  376     // do nothing if already expanded
  377     if(!this.collapsed) {
  378       return this;
  379     }
  380 
  381     // fire beforeexpand event
  382     if(false === this.fireEvent('beforeexpand', this)) {
  383       return this;
  384     }
  385 
  386     // reset collapsed flag
  387     this.collapsed = false;
  388 
  389     // hide shadow
  390     if(!this.docked) {
  391       this.setShadow(false);
  392     }
  393 
  394     // enable resizing
  395     if(this.resizer && !this.docked) {
  396       this.setResizable(true);
  397     }
  398 
  399     // animate expand
  400     if(this.animate) {
  401         this.body.slideIn('t', {
  402           easing: this.easingExpand || null
  403           , scope: this
  404           , duration: this.duration
  405           , callback: this.updateVisuals
  406         });
  407     }
  408 
  409     // don't animate, just show
  410     else {
  411       this.body.show();
  412       this.updateVisuals();
  413       this.fireEvent('animationcompleted', this);
  414     }
  415 
  416     // fire expand event
  417     this.fireEvent('expand', this);
  418 
  419     return this;
  420 
  421   }
  422   // }}}
  423   // {{{
  424   /**
  425     * Toggles the expanded/collapsed states
  426     * @return {Ext.ux.InfoPanel} this
  427     */
  428   , toggle: function() {
  429       if(this.collapsed) {
  430         this.expand();
  431       }
  432       else {
  433         this.collapse();
  434       }
  435       return this;
  436   }
  437   // }}}
  438   // {{{
  439   /**
  440     * Collapses the panel
  441     * @return {Ext.ux.InfoPanel} this
  442     */
  443   , collapse: function() {
  444 
  445     // do nothing if already collapsed or pinned
  446     if(this.collapsed || this.pinned) {
  447       return this;
  448     }
  449 
  450     // fire beforecollapse event
  451     if(false === this.fireEvent('beforecollapse', this)) {
  452         return this;
  453     }
  454 
  455     if(this.bodyScroll /*&& !Ext.isIE*/) {
  456       this.body.setStyle('overflow','hidden');
  457     }
  458 
  459     // set collapsed flag
  460     this.collapsed = true;
  461 
  462     // hide shadow
  463     this.setShadow(false);
  464 
  465     // disable resizing of collapsed panel
  466     if(this.resizer) {
  467       this.setResizable(false);
  468     }
  469 
  470     // animate collapse
  471     if(this.animate) {
  472         this.body.slideOut('t', {
  473           easing: this.easingCollapse || null
  474           , scope: this
  475           , duration: this.duration
  476           , callback: this.updateVisuals
  477         });
  478     }
  479 
  480     // don't animate, just hide
  481     else {
  482       this.body.hide();
  483       this.updateVisuals();
  484       this.fireEvent('animationcompleted', this);
  485     }
  486 
  487     // fire collapse event
  488     this.fireEvent('collapse', this);
  489 
  490     return this;
  491 
  492   }
  493   // }}}
  494   // {{{
  495   /**
  496     * Called internally to update class of the collapse button
  497     * as part of expand and collapse methods
  498     *
  499     * @return {Ext.ux.InfoPanel} this
  500     */
  501   , updateVisuals: function() {
  502 
  503       // handle collapsed state
  504       if(this.collapsed) {
  505         if(this.showPin) {
  506           this.collapseBtn.show();
  507           this.stickBtn.hide();
  508         }
  509         Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
  510           'x-layout-collapse-south', 'x-layout-collapse-east'
  511         );
  512         this.body.replaceClass('x-dock-panel-body-expanded', 'x-dock-panel-body-collapsed');
  513         this.titleEl.replaceClass('x-dock-panel-title-expanded', 'x-dock-panel-title-collapsed');
  514       }
  515 
  516       // handle expanded state
  517       else {
  518         if(this.showPin) {
  519           if(this.pinned) {
  520             Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stick', 'x-layout-stuck');
  521             this.titleEl.addClass('x-dock-panel-title-pinned');
  522           }
  523           else {
  524             Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stuck', 'x-layout-stick');
  525             this.titleEl.removeClass('x-dock-panel-title-pinned');
  526           }
  527           this.collapseBtn.hide();
  528           this.stickBtn.show();
  529         }
  530         else {
  531           Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
  532             'x-layout-collapse-east', 'x-layout-collapse-south'
  533           );
  534         }
  535         this.body.replaceClass('x-dock-panel-body-collapsed', 'x-dock-panel-body-expanded');
  536         this.titleEl.replaceClass('x-dock-panel-title-collapsed', 'x-dock-panel-title-expanded');
  537       }
  538 
  539       // show shadow if necessary
  540       if(!this.docked) {
  541         this.setShadow(true);
  542       }
  543 
  544       if(this.bodyScroll && !this.docked && !this.collapsed /*&& !Ext.isIE*/) {
  545         this.body.setStyle('overflow', 'auto');
  546       }
  547 
  548       this.constrainToDesktop();
  549 
  550       // fire animationcompleted event
  551       this.fireEvent('animationcompleted', this);
  552 
  553       // clear visibility style of body's children
  554       var kids = this.body.select('div[className!=x-grid-viewport],input{visibility}');
  555 //      var kids = this.body.select('div,input{visibility}');
  556       kids.setStyle.defer(1, kids, ['visibility','']);
  557 
  558       // restore visibility of grid-viewport if any
  559 //      var gvp = this.body.select('div.x-grid-viewport');
  560 //      gvp.setStyle('visibility', this.collapsed ? 'hidden' : 'visible');
  561 
  562       return this;
  563   }
  564   // }}}
  565   // {{{
  566   /**
  567     * Creates toolbar
  568     * @param {Array} config Configuration for Ext.Toolbar
  569     * @param {Boolean} bottom true to create bottom toolbar. (defaults to false = top toolbar)
  570     * @return {Ext.Toolbar} Ext.Toolbar object
  571     */
  572   , createToolbar: function(config, bottom) {
  573       var create = {tag:'div'}, tbEl;
  574       config = config || null;
  575       if(bottom) {
  576         tbEl = Ext.DomHelper.append(this.body, create, true);
  577         tbEl.addClass('x-dock-panel-toolbar-bottom');
  578       }
  579       else {
  580         tbEl = Ext.DomHelper.insertFirst(this.body, create, true);
  581         tbEl.addClass('x-dock-panel-toolbar');
  582       }
  583       this.toolbar = new Ext.Toolbar(tbEl, config);
  584       return this.toolbar;
  585   }
  586   // }}}
  587   // {{{
  588   /**
  589     * Set the panel draggable
  590     * Uses lazy creation of dd object
  591     * @param {Boolean} enable pass false to disable dragging
  592     * @return {Ext.ux.InfoPanel} this
  593     */
  594   , setDraggable: function(enable) {
  595 
  596     if(!this.draggable) {
  597       return this;
  598     }
  599 
  600     // lazy create proxy
  601     var dragTitleEl;
  602     if(!this.proxy) {
  603       this.proxy = this.el.createProxy('x-dlg-proxy');
  604 
  605       // setup title
  606       dragTitleEl = Ext.DomHelper.append(this.proxy, {tag:'div'}, true);
  607       dragTitleEl.update(this.el.dom.firstChild.innerHTML);
  608       dragTitleEl.dom.className = this.el.dom.firstChild.className;
  609       if(this.collapsed && Ext.isIE) {
  610         dragTitleEl.dom.style.borderBottom = "0";
  611       }
  612 
  613       this.proxy.hide();
  614       this.proxy.setOpacity(0.5);
  615       this.dd = new Ext.dd.DDProxy(this.el.dom, 'PanelDrag', {
  616         dragElId: this.proxy.id
  617         , scroll: false
  618       });
  619       this.dd.scroll = false;
  620       this.dd.afterDrag = function() {
  621         if(this.panel && this.panel.shadow && !this.panel.docked) {
  622           this.panel.shadow.show(this.panel.el);
  623         }
  624       };
  625 
  626       this.constrainToDesktop();
  627       Ext.EventManager.onWindowResize(this.moveToViewport, this);
  628     }
  629 
  630     this.dd.panel = this;
  631     this.dd.setHandleElId(this.titleEl.id);
  632     if(false === enable) {
  633       this.dd.lock();
  634     }
  635     else {
  636       this.dd.unlock();
  637     }
  638 
  639     return this;
  640   }
  641   // }}}
  642   // {{{
  643   /**
  644     * Set the panel resizable
  645     * Uses lazy creation of the resizer object
  646     * @param {Boolean} pass false to disable resizing
  647     * @return {Ext.ux.InfoPanel} this
  648     */
  649   , setResizable: function(enable) {
  650 
  651     if(!this.resizable) {
  652       return this;
  653     }
  654 
  655     // {{{
  656     // lazy create resizer
  657     if(!this.resizer) {
  658 
  659       // {{{
  660       // create resizer
  661       this.resizer = new Ext.Resizable(this.el, {
  662         handles: 's w e sw se'
  663         , minWidth: this.minWidth || this.tm.getWidth(this.getTitle()) + 56 || 48
  664         , maxWidth: this.maxWidth || 9999
  665         , minHeight: this.minHeight || 48
  666         , maxHeight: this.maxHeight || 9999
  667         , transparent: true
  668         , draggable: false
  669       });
  670       // }}}
  671       // {{{
  672       // install event handlers
  673       this.resizer.on({
  674         beforeresize: {
  675           scope:this
  676           , fn: function(resizer, e) {
  677             var viewport = this.getViewport();
  678             var box = this.getBox();
  679 
  680             var pos = resizer.activeHandle.position;
  681 
  682             // left constraint
  683             if(pos.match(/west/)) {
  684               resizer.minX = viewport.x + (this.dragPadding.left || 8);
  685             }
  686 
  687             // down constraint
  688             if(pos.match(/south/)) {
  689               resizer.oldMaxHeight = resizer.maxHeight;
  690               resizer.maxHeight = viewport.y + viewport.height - box.y - (this.dragPadding.bottom || 8);
  691             }
  692 
  693             // right constraint
  694             if(pos.match(/east/)) {
  695               resizer.oldMaxWidth = resizer.maxWidth;
  696               resizer.maxWidth = viewport.x + viewport.width - box.x - (this.dragPadding.right || 10);
  697             }
  698         }}
  699         , resize: {
  700           scope: this
  701           , fn: function(resizer, width, height, e) {
  702             resizer.maxHeight = resizer.oldMaxHeight || resizer.maxHeight;
  703             resizer.maxWidth = resizer.oldMaxWidth || resizer.maxWidth;
  704             this.setSize(width, height);
  705             this.constrainToDesktop();
  706             this.fireEvent('boxchange', this, this.el.getBox());
  707             this.fireEvent('resize', this, width, height);
  708         }}
  709       });
  710       // }}}
  711 
  712     }
  713     // }}}
  714 
  715     this.resizer.enabled = enable;
  716 
  717     // this is custom override of Ext.Resizer
  718     this.resizer.showHandles(enable);
  719 
  720     return this;
  721   }
  722   // }}}
  723   // {{{
  724   /**
  725     * Called internally to clip passed width and height to viewport
  726     * @param {Integer} w width
  727     * @param {Integer} h height
  728     * @return {Object} {width:safeWidth, height:safeHeight}
  729     */
  730   , safeSize: function(w, h) {
  731     var viewport = this.getViewport();
  732     var box = this.getBox();
  733     var gap = 0;
  734     var safeSize = {width:w, height:h};
  735 
  736     safeSize.height =
  737       box.y + h + this.dragPadding.bottom + gap > viewport.height + viewport.y
  738       ? viewport.height - box.y + viewport.y - this.dragPadding.bottom - gap
  739       : safeSize.height
  740     ;
  741 
  742     safeSize.width =
  743       box.x + w + this.dragPadding.right + gap > viewport.width + viewport.x
  744       ? viewport.width - box.x + viewport.x - this.dragPadding.right - gap
  745       : safeSize.width
  746     ;
  747 
  748     return safeSize;
  749   }
  750   // }}}
  751   // {{{
  752   /**
  753     * Called internally to get current viewport
  754     * @param {Element/HTMLElement/String} desktop Element to get size and position of
  755     * @return {Object} viewport {x:x, y:y, width:width, height:height} x and y are page coords
  756     */
  757   , getViewport: function(desktop) {
  758 
  759     desktop = desktop || this.desktop || document.body;
  760     var viewport = Ext.get(desktop).getViewSize();
  761     var xy;
  762     if(document.body === desktop.dom) {
  763       viewport.x = 0;
  764       viewport.y = 0;
  765     }
  766     else {
  767       xy = desktop.getXY();
  768       viewport.x = isNaN(xy[0]) ? 0 : xy[0];
  769       viewport.y = isNaN(xy[1]) ? 0 : xy[1];
  770     }
  771 
  772     return viewport;
  773   }
  774   // }}}
  775   // {{{
  776   /**
  777     * Sets the size of the panel. Demanded size is clipped to the viewport
  778     *
  779     * @param {Integer} w width to set
  780     * @param {Integer} h height to set
  781     * @return {Ext.ux.InfoPanel} this
  782     */
  783   , setSize: function(w, h) {
  784     var safeSize = this.safeSize(w, h);
  785     this.setWidth(safeSize.width);
  786     this.setHeight(safeSize.height);
  787 
  788     if(!this.docked) {
  789       this.setShadow(true);
  790     }
  791   }
  792   // }}}
  793   // {{{
  794   /**
  795     * Sets the width of the panel. Demanded width is clipped to the viewport
  796     *
  797     * @param {Integer} w width to set
  798     * @return {Ext.ux.InfoPanel} this
  799     */
  800   , setWidth: function(w) {
  801     this.el.setWidth(w);
  802     this.body.setStyle('width','');
  803     if(!this.docked) {
  804       this.setShadow(true);
  805     }
  806     this.lastWidth = w;
  807 
  808     return this;
  809   }
  810   // }}}
  811   // {{{
  812   /**
  813     * Sets the height of the panel. Demanded height is clipped to the viewport
  814     *
  815     * @param {Integer} h height to set
  816     * @return {Ext.ux.InfoPanel} this
  817     */
  818   , setHeight: function(h) {
  819     var newH = h - this.titleEl.getHeight();
  820     if(0 < newH) {
  821       this.body.setHeight(newH);
  822     }
  823     else {
  824       this.body.setStyle('height','');
  825     }
  826 
  827     if(!this.docked) {
  828       this.setShadow(true);
  829     }
  830 //    this.lastHeight = h;
  831     this.el.setStyle('height','');
  832 
  833     return this;
  834   }
  835   // }}}
  836   // {{{
  837   /**
  838     * Called internally to set x, y, width and height of the panel
  839     *
  840     * @param {Object} box
  841     * @return {Ext.ux.InfoPanel} this
  842     */
  843   , setBox: function(box) {
  844     this.el.setBox(box);
  845     this.moveToViewport();
  846     this.setSize(box.width, box.height);
  847 
  848     return this;
  849   }
  850   // }}}
  851   // {{{
  852   /**
  853     * Called internally to get the box of the panel
  854     *
  855     * @return {Object} box
  856     */
  857   , getBox: function() {
  858     return this.el.getBox();
  859   }
  860   // }}}
  861   // {{{
  862   /**
  863     * Turns shadow on/off
  864     * Uses lazy creation of the shadow object
  865     * @param {Boolean} shadow pass false to hide, true to show the shadow
  866     * @return {Ext.ux.InfoPanel} this
  867     */
  868   , setShadow: function(shadow) {
  869 
  870     // if I have shadow but shouldn't use it
  871     if(this.shadow && !this.useShadow) {
  872       this.shadow.hide();
  873       return this;
  874     }
  875 
  876     // if I shouldn't use shadow
  877     if(!this.useShadow) {
  878       return this;
  879     }
  880 
  881     // if I don't have shadow
  882     if(!this.shadow) {
  883       this.shadow = new Ext.Shadow({mode:this.shadowMode});
  884     }
  885 
  886     // show or hide
  887     var zindex;
  888     if(shadow) {
  889       this.shadow.show(this.el);
  890 
  891       // fix the Ext shadow z-index bug
  892       zindex = parseInt(this.el.getStyle('z-index'), 10);
  893       zindex = isNaN(zindex) ? '' : zindex - 1;
  894       this.shadow.el.setStyle('z-index', zindex);
  895     }
  896     else {
  897       this.shadow.hide();
  898     }
  899 
  900     return this;
  901 
  902   }
  903   // }}}
  904   // {{{
  905   /**
  906     * Show the panel
  907     * @param {Boolean} show (optional) if false hides the panel instead of show
  908     * @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
  909     * @return {Ext.ux.InfoPanel} this
  910     */
  911   , show: function(show, alsoUndocked) {
  912 
  913     // ignore undocked panels if not forced to
  914     if(!this.docked && true !== alsoUndocked) {
  915       return this;
  916     }
  917 
  918     show = (false === show ? false : true);
  919     if(!this.docked) {
  920       this.setShadow(show);
  921     }
  922 
  923     this.el.setStyle('display', show ? '' : 'none');
  924     return this;
  925   }
  926   // }}}
  927   // {{{
  928   /**
  929     * Hide the panel
  930     * @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
  931     * @return {Ext.ux.InfoPanel} this
  932     */
  933   , hide: function(alsoUndocked) {
  934     this.show(false, alsoUndocked);
  935   }
  936   // }}}
  937   // {{{
  938   /**
  939     * Constrains dragging of this panel to desktop boundaries
  940     * @param {Element} desktop the panel is to be constrained to
  941     * @return {Ext.ux.InfoPanel} this
  942     */
  943   , constrainToDesktop: function(desktop) {
  944     desktop = desktop || this.desktop;
  945     if(desktop && this.dd) {
  946       this.dd.constrainTo(desktop, this.dragPadding, false);
  947     }
  948     return this;
  949   }
  950   // }}}
  951   // {{{
  952   /**
  953     * Called internally to move the panel to the viewport.
  954     * Also constrains the dragging to the desktop
  955     *
  956     * @param {Object} viewport (optional) object {x:x, y:y, width:width, height:height}
  957     * @return {Ext.ux.InfoPanel} this
  958     */
  959   , moveToViewport: function(viewport) {
  960     viewport = viewport && !isNaN(viewport.x) ? viewport : this.getViewport();
  961     var box = this.getBox();
  962     var moved = false;
  963     var gap = 10;
  964 
  965     // horizontal
  966     if(box.x + box.width + this.dragPadding.right > viewport.x + viewport.width) {
  967       moved = true;
  968       box.x = viewport.width + viewport.x - box.width - this.dragPadding.right - gap;
  969     }
  970     if(box.x - this.dragPadding.left < viewport.x) {
  971       moved = true;
  972       box.x = viewport.x + this.dragPadding.left + gap;
  973     }
  974 
  975     // vertical
  976     if(box.y + box.height + this.dragPadding.bottom > viewport.y + viewport.height) {
  977       moved = true;
  978       box.y = viewport.height + viewport.y - box.height - this.dragPadding.bottom - gap;
  979     }
  980     if(box.y - this.dragPadding.top < viewport.y) {
  981       moved = true;
  982       box.y = viewport.y + this.dragPadding.top + gap;
  983     }
  984 
  985     var oldOverflow;
  986     if(moved) {
  987       // sanity clip
  988       box.x = box.x < viewport.x ? viewport.x : box.x;
  989       box.y = box.y < viewport.y ? viewport.y : box.y;
  990 
  991       // prevent scrollbars from appearing
  992       this.desktop.oldOverflow = this.desktop.oldOverflow || this.desktop.getStyle('overflow');
  993       this.desktop.setStyle('overflow', 'hidden');
  994 
  995       // set position
  996       this.el.setXY([box.x, box.y]);
  997 
  998       // restore overflow
  999       this.desktop.setStyle.defer(100, this.desktop, ['overflow', this.desktop.oldOverflow]);
 1000 
 1001       if(!this.docked) {
 1002         this.setShadow(true);
 1003       }
 1004     }
 1005 
 1006     this.constrainToDesktop();
 1007 
 1008     return this;
 1009   }
 1010 // }}}
 1011 
 1012   /**
 1013     * destroys the panel
 1014     */
 1015   , destroy: function() {
 1016     if(this.shadow) {
 1017       this.shadow.hide();
 1018     }
 1019     if(this.collapsible) {
 1020       this.collapseBtn.removeAllListeners();
 1021       this.titleEl.removeAllListeners();
 1022     }
 1023 
 1024     if(this.resizer) {
 1025       this.resizer.destroy();
 1026     }
 1027     if(this.dd) {
 1028       if(this.proxy) {
 1029         this.proxy.removeAllListeners();
 1030         this.proxy.remove();
 1031       }
 1032       this.dd.unreg();
 1033       this.dd = null;
 1034     }
 1035 
 1036     this.body.removeAllListeners();
 1037 
 1038     // call parent destroy
 1039     Ext.ux.InfoPanel.superclass.destroy.call(this);
 1040 
 1041     this.fireEvent('destroy', this);
 1042 
 1043   }
 1044 
 1045 }); // end of extend
 1046 
 1047 // {{{
 1048 // show/hide resizer handles override
 1049 Ext.override(Ext.Resizable, {
 1050 
 1051   /**
 1052     * Hide resizer handles
 1053     */
 1054   hideHandles: function() {
 1055     this.showHandles(false);
 1056   } // end of function hideHandles
 1057 
 1058   /**
 1059     * Show resizer handles
 1060     *
 1061     * @param {Boolean} show (true = show, false = hide)
 1062     */
 1063   , showHandles: function(show) {
 1064     show = (false === show ? false : true);
 1065     var pos;
 1066     for(var p in Ext.Resizable.positions) {
 1067       pos = Ext.Resizable.positions[p];
 1068       if(this[pos]) {
 1069         this[pos].el.setStyle('display', show ? '' : 'none');
 1070       }
 1071     }
 1072   } // end of function showHandles
 1073 // }}}
 1074 
 1075 });
 1076 
 1077 // end of file