Ext.ux.Accordion extension class code:
    1 // vim: ts=2:sw=2:nu:fdc=2:nospell
    2 
    3 // Create user extensions namespace (Ext.ux)
    4 Ext.namespace('Ext.ux');
    5 
    6 /**
    7   * Ext.ux.Accordion Extension Class
    8   *
    9   * @author  Ing. Jozef Sakalos
   10   * @version $Id: Ext.ux.Accordion.js 18 2007-06-13 17:55:16Z jozo $
   11   *
   12   * @class Ext.ux.Accordion
   13   * @extends Ext.ContentPanel
   14   * @constructor
   15   * @param {String/HTMLElement/Element} el The container element for this panel
   16   * @param {String/Object} config A string to set only the title or a config object
   17   * @cfg {Boolean} animate global animation flag for all panels. (defaults to true)
   18   * @cfg {Boolean} boxWrap set to true to wrap wrapEl the body is child of (defaults to false)
   19   * @cfg {Boolean} draggable set to false to disallow panels dragging (defaults to true)
   20   * @cfg {Boolean} fitHeight set to true if you use fixed height dock
   21   * @cfg {Boolean} independent true to make panels independent (defaults to false)
   22   * @cfg {Integer} initialHeight Initial height to set box to (defaults to 0)
   23   * @cfg {Boolean} monitorWindowResize if true panels are moved to
   24   *  viewport if window is small (defaults to true)
   25   * @cfg {Boolean} resizable global resizable flag for all panels (defaults to true)
   26   * @cfg {Boolean} undockable true to allow undocking of panels (defaults to true)
   27   * @cfg {Boolean} useShadow global useShadow flag for all panels. (defaults to true)
   28   * @cfg {Element/HTMLElement/String} wrapEl Element to wrap with nice surrounding
   29   */
   30 Ext.ux.Accordion = function(el, config) {
   31 
   32   // call parent constructor
   33   Ext.ux.Accordion.superclass.constructor.call(this, el, config);
   34 
   35   // create collection for panels
   36   this.items = new Ext.util.MixedCollection();
   37 
   38   // assume no panel is expanded
   39   this.expanded = null;
   40 
   41   // {{{
   42   // install event handlers
   43   this.on({
   44 
   45     // {{{
   46     // runs before expansion. Triggered by panel's beforeexpand event
   47     beforeexpand: {
   48         scope: this
   49       , fn: function(panel) {
   50           // raise panel above others
   51           if(!panel.docked) {
   52             this.raise(panel);
   53           }
   54 
   55           // set fixed height
   56           var panelBodyHeight;
   57           if(this.fitHeight && panel.docked) {
   58             panelBodyHeight = this.getPanelBodyHeight();
   59             if(panelBodyHeight) {
   60               panel.body.setHeight(panelBodyHeight);
   61             }
   62           }
   63 
   64           if(panel.docked) {
   65             this.expandCount++;
   66             this.expanding = true;
   67 //            this.setDockScroll(false);
   68           }
   69 
   70           // don't collapse others if independent or not docked
   71           if(this.independent || !panel.docked) {
   72             return this;
   73           }
   74 
   75           // collapse expanded panel
   76           if(this.expanded && this.expanded.docked) {
   77             this.expanded.collapse();
   78           }
   79 
   80           // remember this panel as expanded
   81           this.expanded = panel;
   82     }}
   83     // }}}
   84     // {{{
   85     // runs before panel collapses. Triggered by panel's beforecollapse event
   86     , beforecollapse: {
   87         scope: this
   88       , fn: function(panel) {
   89 
   90         // raise panel if not docked
   91         if(!panel.docked) {
   92           this.raise(panel);
   93         }
   94         return this;
   95     }}
   96     // }}}
   97     // {{{
   98     // runs on when panel expands (before animation). Triggered by panel's expand event
   99     , expand: {
  100         scope: this
  101       , fn: function(panel) {
  102         this.fireEvent('panelexpand', panel);
  103     }}
  104     // }}}
  105     // {{{
  106     // runs on when panel collapses (before animation). Triggered by panel's collapse event
  107     , collapse: {
  108         scope: this
  109       , fn: function(panel) {
  110         this.fireEvent('panelcollapse', panel);
  111     }}
  112     // }}}
  113     // {{{
  114     // runs on when animation is completed. Triggered by panel's animationcompleted event
  115     , animationcompleted: {
  116       scope: this
  117       , fn: function(panel) {
  118         var box = panel.el.getBox();
  119         this.expandCount = (this.expandCount && this.expanding) ? --this.expandCount : 0;
  120         if((0 === this.expandCount) && this.expanding) {
  121 //          this.setDockScroll(true);
  122           this.expanding = false;
  123         }
  124         this.fireEvent('panelbox', panel, box);
  125     }}
  126     // }}}
  127     // {{{
  128     // runs when panel is pinned. Triggered by panel's pinned event
  129     , pinned: {
  130         scope: this
  131       , fn: function(panel, pinned) {
  132         if(!pinned) {
  133           if(panel.collapseOnUnpin) {
  134             panel.collapse();
  135           }
  136           else if(!this.independent) {
  137             this.items.each(function(p) {
  138               if(p !== panel && p.docked && !p.pinned) {
  139                 p.collapse();
  140               }
  141             });
  142             this.expanded = panel;
  143           }
  144         }
  145         this.fireEvent('panelpinned', panel, pinned);
  146     }}
  147     // }}}
  148 
  149     , destroy: {
  150       scope:this
  151       , fn: function(panel) {
  152         this.items.removeKey(panel.id);
  153         this.updateOrder();
  154     }}
  155   });
  156   // }}}
  157   // {{{
  158   // add events
  159   this.addEvents({
  160     /**
  161       * Fires when a panel of the dock is collapsed
  162       * @event panelcollapse
  163       * @param {Ext.ux.InfoPanel} panel
  164       */
  165     panelcollapse: true
  166 
  167     /**
  168       * Fires when a panel of the dock is expanded
  169       * @event panelexpand
  170       * @param {Ext.ux.InfoPanel} panel
  171       */
  172     , panelexpand: true
  173 
  174     /**
  175       * Fires when a panel of the dock is pinned
  176       * @event panelpinned
  177       * @param {Ext.ux.InfoPanel} panel
  178       * @param {Boolean} pinned true if panel was pinned false if unpinned
  179       */
  180     , panelpinned: true
  181 
  182     /**
  183       * Fires when the independent state of dock changes
  184       * @event independent
  185       * @param {Ext.ux.Accordion} this
  186       * @param {Boolean} independent New independent state
  187       */
  188     , independent: true
  189 
  190     /**
  191       * Fires when the order of panel is changed
  192       * @event orderchange
  193       * @param {Ext.ux.Accordion} this
  194       * @param {Array} order New order array
  195       */
  196     , orderchange: true
  197 
  198     /**
  199       * Fires when the undockable state of dock changes
  200       * @event undockable
  201       * @param {Ext.ux.Accordion} this
  202       * @param {Array} undockable New undockable state
  203       */
  204     , undockable: true
  205 
  206     /**
  207       * Fires when a panel is undocked
  208       * @event panelundock
  209       * @param {Ext.ux.InfoPanel} panel
  210       * @param {Object} box Position and size object
  211       */
  212     , panelundock: true
  213 
  214     /**
  215       * Fires when a panel is undocked
  216       * @event paneldock
  217       * @param {Ext.ux.InfoPanel} panel
  218       */
  219     , paneldock: true
  220 
  221     /**
  222       * Fires when a panel box is changed, e.g. after dragging
  223       * @event panelbox
  224       * @param {Ext.ux.InfoPanel} panel
  225       * @param {Object} box Position and size object
  226       */
  227     , panelbox: true
  228 
  229     /**
  230       * Fires when useShadow status changes
  231       * @event useshadow
  232       * @param {Ext.ux.Accordion} this
  233       * @param {Boolean} shadow Use shadow (for undocked panels) flag
  234       */
  235     , useshadow: true
  236   });
  237   // }}}
  238 
  239   // setup body
  240   this.body = Ext.get(this.body) || this.el;
  241   this.resizeEl = this.body;
  242   this.id = this.el.id;
  243   this.body.addClass('x-dock-body');
  244 
  245   // setup desktop
  246   this.desktop = Ext.get(this.desktop || document.body);
  247   //this.desktop = this.desktop.dom || this.desktop;
  248 
  249   // setup fixed hight
  250   this.wrapEl = Ext.get(this.wrapEl);
  251   if(this.fitHeight) {
  252     this.body.setStyle('overflow', 'hidden');
  253 //    this.bodyHeight = this.initialHeight || this.body.getHeight();
  254     this.body.setHeight(this.initialHeight || this.body.getHeight());
  255     if(this.boxWrap && this.wrapEl) {
  256       this.wrapEl.boxWrap();
  257     }
  258   }
  259 
  260   // watch window resize
  261   if(this.monitorWindowResize) {
  262     Ext.EventManager.onWindowResize(this.adjustViewport, this);
  263   }
  264 
  265   // create drop zone for panels
  266   this.dd = new Ext.dd.DropZone(this.body.dom, {ddGroup:'dock-' + this.id });
  267 
  268 }; // end of constructor
  269 
  270 // extend
  271 Ext.extend(Ext.ux.Accordion, Ext.ContentPanel, {
  272 
  273   // {{{
  274   // defaults
  275   independent: false
  276   , undockable: true
  277   , useShadow: true
  278   , boxWrap: false
  279   , fitHeight: false
  280   , initialHeight: 0
  281   , animate: true // global animation flag
  282   , zindex: 9999 // (private)
  283   , zindexInc: 2 // (private) one for shadow
  284   , expandCount: 0
  285   , expanding: false
  286   , monitorWindowResize: true
  287   , resizable: true // global resizable flag
  288   , draggable: true // global draggable flag
  289   // }}}
  290   // {{{
  291   /**
  292     * Adds the panel to Accordion
  293     * @param {Ext.ux.InfoPanel} panel Panel to add
  294     * @return {Ext.ux.InfoPanel} added panel
  295     */
  296   , add: function(panel) {
  297 
  298 
  299     // append panel to body
  300     this.body.appendChild(panel.el);
  301 
  302     panel.docked = true;
  303 
  304     // add docked class to panel body
  305 //    panel.body.addClass('x-dock-panel-body-docked');
  306 //    panel.body.removeClass('x-dock-panel-body-undocked');
  307     panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
  308 
  309     // add panel to items collection
  310     this.items.add(panel.el.id, panel);
  311 
  312     // relay these events from panel to dock
  313     this.relayEvents(panel, [
  314       'beforecollapse'
  315       , 'collapse'
  316       , 'beforeexpand'
  317       , 'expand'
  318       , 'animationcompleted'
  319       , 'pinned'
  320       , 'boxchange'
  321       , 'destroy'
  322     ]);
  323 
  324     // panel dragging
  325     if(this.draggable) {
  326       panel.dd = new Ext.ux.Accordion.DDDock(panel, 'dock-' + this.id, this);
  327     }
  328 
  329     // panel resizing
  330     panel.resizable = this.resizable;
  331 //    panel.setResizable(this.resizable);
  332 
  333     // shadow and animate flags
  334     panel.useShadow = this.useShadow;
  335     panel.setShadow(this.useShadow);
  336     if(panel.shadow) {
  337       panel.shadow.hide();
  338     }
  339     panel.animate = undefined === panel.animate ? this.animate : panel.animate;
  340 
  341     // z-index for panel
  342     this.zindex += this.zindexInc;
  343     panel.zindex = this.zindex;
  344 
  345     // onclick handler for panel body (allows raising when panel body is clicked)
  346     panel.body.on('click', this.onClickPanelBody.createDelegate(this, [panel]));
  347 
  348     if(this.fitHeight) {
  349       this.setPanelHeight(panel);
  350     }
  351 
  352     panel.dock = this;
  353     panel.desktop = this.desktop;
  354 
  355     return panel;
  356 
  357   }
  358   // }}}
  359   // {{{
  360   /**
  361     * Called internally to raise panel above others
  362     * Maintains z-index stack
  363     * @param {Ext.ux.InfoPanel} panel Panel to raise
  364     */
  365   , raise: function(panel) {
  366     this.items.each(function(p) {
  367       if(p.zindex > panel.zindex) {
  368         p.zindex -= this.zindexInc;
  369         p.el.applyStyles({'z-index':p.zindex});
  370         if(!p.docked) {
  371           p.setShadow(true);
  372         }
  373       }
  374     }, this);
  375     panel.zindex = this.zindex;
  376     panel.el.applyStyles({'z-index':panel.zindex});
  377     if(this.desktop.lastChild !== panel.el.dom) {
  378       this.desktop.appendChild(panel.el.dom);
  379     }
  380     if(!panel.docked) {
  381       panel.setShadow(true);
  382     }
  383   }
  384   // }}}
  385   // {{{
  386   /**
  387     * Resets the order of panels within the dock
  388     *
  389     * @return {Ext.ux.Accordion} this
  390     */
  391   , resetOrder: function() {
  392     this.items.each(function(panel) {
  393       if(!panel.docked) {
  394         return;
  395       }
  396       this.body.appendChild(panel.el);
  397     }, this);
  398     this.updateOrder();
  399     return this;
  400   }
  401   // }}}
  402   // {{{
  403   /**
  404     * Called internally to update the order variable after dragging
  405     */
  406   , updateOrder: function() {
  407     var order = [];
  408     var titles = this.body.select('.x-layout-panel-hd');
  409     titles.each(function(titleEl){
  410       order.push(titleEl.dom.parentNode.id);
  411     });
  412     this.order = order;
  413     this.fireEvent('orderchange', this, order);
  414   }
  415   // }}}
  416   // {{{
  417   /**
  418     * Returns array of panel ids in the current order
  419     * @return {Array} order of panels
  420     */
  421   , getOrder: function() {
  422     return this.order;
  423   }
  424   // }}}
  425   // {{{
  426   /**
  427     * Set the order of panels
  428     * @param {Array} order Array of ids of panels in required order.
  429     * @return {Ext.ux.Accordion} this
  430     */
  431   , setOrder: function(order) {
  432     if('object' !== typeof order || undefined === order.length) {
  433       throw "setOrder: Argument is not array.";
  434     }
  435     var panelEl;
  436     for(var i = 0; i < order.length; i++) {
  437       panelEl = Ext.get(order[i]);
  438       if(panelEl) {
  439         this.body.appendChild(panelEl);
  440       }
  441     }
  442     this.updateOrder();
  443     return this;
  444   }
  445   // }}}
  446   // {{{
  447   /**
  448     * Collapse all docked panels
  449     * @param {Boolean} alsoPinned true to first unpin then collapse
  450     * @param {Ext.ux.InfoPanel} except This panel will not be collapsed.
  451     * @return {Ext.ux.Accordion} this
  452     */
  453   , collapseAll: function(alsoPinned, except) {
  454     this.items.each(function(panel) {
  455       if(panel.docked) {
  456         panel.pinned = alsoPinned ? false : panel.pinned;
  457         if(!except || panel !== except) {
  458           panel.collapse();
  459         }
  460       }
  461     }, this);
  462     return this;
  463   }
  464   // }}}
  465   // {{{
  466   /**
  467     * Expand all docked panels in independent mode
  468     * @return {Ext.ux.Accordion} this
  469     */
  470   , expandAll: function() {
  471     if(this.independent) {
  472       this.items.each(function(panel) {
  473         if(panel.docked && panel.collapsed) {
  474           panel.expand();
  475         }
  476       }, this);
  477     }
  478   }
  479   // }}}
  480   // {{{
  481   /**
  482     * Called internally while dragging and by state manager
  483     * @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
  484     * @box {Object} box coordinates with target position and size
  485     * @return {Ext.ux.Accordion} this
  486     */
  487   , undock: function(panel, box) {
  488 
  489     // get panel if necessary
  490     panel = 'string' === typeof panel ? this.items.get(panel) : panel;
  491 
  492     // proceed only if we have docked panel and in undockable mode
  493     if(panel && panel.docked && this.undockable) {
  494 
  495       // sanity check
  496       if(box.x < 0 || box.y < 0) {
  497         return this;
  498       }
  499 
  500       // move the panel in the dom (append to desktop)
  501       this.desktop.appendChild(panel.el.dom);
  502 
  503       // adjust panel visuals
  504       panel.el.applyStyles({
  505         position:'absolute'
  506         , 'z-index': panel.zindex
  507       });
  508       panel.body.replaceClass('x-dock-panel-body-docked', 'x-dock-panel-body-undocked');
  509 
  510       // position the panel
  511       panel.setBox(box);
  512 
  513       // reset docked flag
  514       panel.docked = false;
  515 
  516       // hide panel shadow (will be shown by raise)
  517       if(panel.shadow) {
  518         panel.shadow.hide();
  519       }
  520 
  521       // raise panel above others
  522       this.raise(panel);
  523 
  524       // set the height of a docked expanded panel
  525       this.setPanelHeight(this.expanded);
  526 
  527       // enable resizing and scrolling
  528       panel.setResizable(!panel.collapsed);
  529       if(panel.bodyScroll) {
  530         panel.body.setStyle('overflow','auto');
  531       }
  532 
  533       // size the undocked panel
  534       // todo: revise
  535       panel.lastWidth = box.width;
  536       panel.lastHeight = box.height;
  537 
  538       // fire panelundock event
  539       this.fireEvent('panelundock', panel, {x:box.x, y:box.y, width:box.width, height:box.height});
  540     }
  541 
  542     return this;
  543   }
  544   // }}}
  545   // {{{
  546   /**
  547     * Called internally while dragging
  548     * @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
  549     * @param {String} targetId id of panel after which this panel will be docked
  550     * @return {Ext.ux.Accordion} this
  551     */
  552   , dock: function(panel, targetId) {
  553 
  554     // get panel if necessary
  555     panel = 'string' === typeof panel ? this.items.get(panel) : panel;
  556 
  557     // proceed only if we have a docked panel
  558     if(panel && !panel.docked) {
  559 
  560       // remember width and height
  561       if(!panel.collapsed) {
  562         panel.lastWidth = panel.el.getWidth();
  563         panel.lastHeight = panel.el.getHeight();
  564       }
  565 
  566       // move the panel element in the dom
  567       if(targetId && (this.body.id !== targetId)) {
  568         panel.el.insertBefore(Ext.fly(targetId));
  569       }
  570       else {
  571         panel.el.appendTo(this.body);
  572       }
  573 
  574       // set docked flag
  575       panel.docked = true;
  576 
  577       // adjust panel visuals
  578       panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
  579       panel.el.applyStyles({
  580         top:''
  581         , left:''
  582         , width:''
  583         , height:''
  584         , 'z-index':''
  585         , position:'relative'
  586         , visibility:''
  587       });
  588       panel.body.applyStyles({width:'',height:''});
  589 
  590       // disable resizing and shadow
  591       panel.setResizable(false);
  592       if(panel.shadow) {
  593         panel.shadow.hide();
  594       }
  595 
  596       // set panel height (only if this.fitHeight = true)
  597       this.setPanelHeight(panel.collapsed ? this.expanded : panel);
  598 
  599       // fire paneldock event
  600       this.fireEvent('paneldock', panel);
  601     }
  602 
  603     return this;
  604   }
  605   // }}}
  606   // {{{
  607   /**
  608     * Sets the independent mode
  609     * @param {Boolean} independent set to false for normal mode
  610     * @return {Ext.ux.Accordion} this
  611     */
  612   , setIndependent: function(independent) {
  613     this.independent = independent ? true : false;
  614     this.fireEvent('independent', this, independent);
  615     return this;
  616   }
  617   // }}}
  618   // {{{
  619   /**
  620     * Sets the undockable mode
  621     * If undockable === true all undocked panels are docked and collapsed (except pinned)
  622     * @param {Boolean} undockable set to true to not allow undocking
  623     * @return {Ext.ux.Accordion} this
  624     */
  625   , setUndockable: function(undockable) {
  626     this.items.each(function(panel) {
  627 
  628       // dock and collapse (except pinned) all undocked panels if not undockable
  629       if(!undockable && !panel.docked) {
  630         this.dock(panel);
  631         if(!this.independent && !panel.collapsed && !panel.pinned) {
  632           panel.collapse();
  633         }
  634       }
  635 
  636       // refresh dragging constraints
  637       if(panel.docked && panel.draggable) {
  638         panel.dd.constrainTo(this.body, 0, false);
  639         panel.dd.clearConstraints();
  640         if(undockable) {
  641           panel.constrainToDesktop();
  642         }
  643         else {
  644           panel.dd.setXConstraint(0,0);
  645         }
  646       }
  647     }, this);
  648 
  649     // set the flag and fire event
  650     this.undockable = undockable;
  651     this.fireEvent('undockable', this, undockable);
  652     return this;
  653   }
  654   // }}}
  655   // {{{
  656   /**
  657     * Restores state of dock and panels
  658     * @param {Ext.state.Provider} provider (optional) An alternate state provider
  659     */
  660   , restoreState: function(provider) {
  661     if(!provider) {
  662       provider = Ext.state.Manager;
  663     }
  664     var sm = new Ext.ux.AccordionStateManager();
  665     sm.init(this, provider);
  666 
  667   }
  668   // }}}
  669   // {{{
  670   /**
  671     * Sets the shadows for all panels
  672     * @param {Boolean} shadow set to false to disable shadows
  673     * @return {Ext.ux.Accordion} this
  674     */
  675   , setShadow: function(shadow) {
  676     this.items.each(function(panel) {
  677       panel.useShadow = shadow;
  678       panel.setShadow(false);
  679       if(!panel.docked) {
  680         panel.setShadow(shadow);
  681       }
  682     });
  683     this.useShadow = shadow;
  684     this.fireEvent('useshadow', this, shadow);
  685     return this;
  686   }
  687   // }}}
  688 // {{{
  689   /**
  690     * Called when user clicks the panel body
  691     * @param {Ext.ux.InfoPanel} panel
  692     */
  693   , onClickPanelBody: function(panel) {
  694     if(!panel.docked) {
  695       this.raise(panel);
  696     }
  697   }
  698 // }}}
  699   // {{{
  700   /**
  701     * Called internally for fixed height docks to get current height of panel(s)
  702     */
  703   , getPanelBodyHeight: function() {
  704       var titleHeight = 0;
  705       this.items.each(function(panel) {
  706         titleHeight += panel.docked ? panel.titleEl.getHeight() : 0;
  707       });
  708       this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb') + 1;
  709 //      this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb');
  710       return this.panelBodyHeight;
  711   }
  712   // }}}
  713   // {{{
  714   /**
  715     * Sets the height of panel body
  716     * Used with fixed height (fitHeight:true) docs
  717     * @param {Ext.ux.InfoPanel} panel (defaults to this.expanded)
  718     * @return {Ext.ux.Accordion} this
  719     */
  720   , setPanelHeight: function(panel) {
  721     panel = panel || this.expanded;
  722     if(this.fitHeight && panel && panel.docked) {
  723       panel.body.setHeight(this.getPanelBodyHeight());
  724     }
  725     return this;
  726   }
  727   // }}}
  728   // {{{
  729   /**
  730     * Constrains the dragging of panels do the desktop
  731     * @return {Ext.ux.Accordion} this
  732     */
  733   , constrainToDesktop: function() {
  734     this.items.each(function(panel) {
  735       panel.constrainToDesktop();
  736     }, this);
  737     return this;
  738   }
  739   // }}}
  740   // {{{
  741   /**
  742     * Clears dragging constraints of panels
  743     * @return {Ext.ux.Accordion} this
  744     */
  745   , clearConstraints: function() {
  746     this.items.each(function(panel) {
  747       panel.dd.clearConstraints();
  748     });
  749   }
  750   // }}}
  751   // {{{
  752   /**
  753     * Shows all panels
  754     * @param {Boolean} show (optional) if false hides the panels instead of showing
  755     * @param {Boolean} alsoUndocked show also undocked panels (defaults to false)
  756     * @return {Ext.ux.Accordion} this
  757     */
  758   , showAll: function(show, alsoUndocked) {
  759     show = (false === show ? false : true);
  760     this.items.each(function(panel) {
  761       panel.show(show, alsoUndocked);
  762     });
  763     return this;
  764   }
  765   // }}}
  766   // {{{
  767   /**
  768     * Hides all panels
  769     * @param {Boolean} alsoUndocked hide also undocked panels (defaults to false)
  770     * @return {Ext.ux.Accordion} this
  771     */
  772   , hideAll: function(alsoUndocked) {
  773     return this.showAll(false, alsoUndocked);
  774   }
  775   // }}}
  776   // {{{
  777   /**
  778     * Called internally to disable/enable scrolling of the dock while animating
  779     * @param {Boolean} enable true to enable, false to disable
  780     * @return {void}
  781     * @todo not used at present - revise
  782     */
  783   , setDockScroll: function(enable) {
  784     if(enable && !this.fitHeight) {
  785       this.body.setStyle('overflow','auto');
  786     }
  787     else {
  788       this.body.setStyle('overflow','hidden');
  789     }
  790   }
  791   // }}}
  792   // {{{
  793   /**
  794     * Set Accordion size
  795     * Overrides ContentPanel.setSize
  796     *
  797     * @param {Integer} w width
  798     * @param {Integer} h height
  799     * @return {Ext.ux.Accordion} this
  800     */
  801   , setSize: function(w, h) {
  802     // call parent's setSize
  803     Ext.ux.Accordion.superclass.setSize.call(this, w, h);
  804 //    this.body.setHeight(h);
  805     this.setPanelHeight();
  806 
  807     return this;
  808   }
  809   // }}}
  810   // {{{
  811   /**
  812     * Called as windowResize event handler
  813     *
  814     * @todo: review
  815     */
  816   , adjustViewport: function() {
  817     var viewport = this.desktop.dom === document.body ? {} : Ext.get(this.desktop).getBox();
  818 
  819     viewport.height =
  820       this.desktop === document.body
  821       ? window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
  822       : viewport.height
  823     ;
  824 
  825     viewport.width =
  826       this.desktop === document.body
  827       ? window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
  828       : viewport.width
  829     ;
  830 
  831     viewport.x = this.desktop === document.body ? 0 : viewport.x;
  832     viewport.y = this.desktop === document.body ? 0 : viewport.y;
  833 
  834     this.items.each(function(panel) {
  835       if(!panel.docked) {
  836         panel.moveToViewport(viewport);
  837       }
  838     });
  839 
  840   }
  841   // }}}
  842 
  843 }); // end of extend
  844 
  845 // {{{
  846 // {{{
  847 /**
  848   * @class Ext.ux.Accordion.DDDock
  849   * @constructor
  850   * @extends Ext.dd.DDProxy
  851   * @param {Ext.ux.InfoPanel} panel Panel the dragging object is created for
  852   * @param {String} group Only elements of same group interact
  853   * @param {Ext.ux.Accordion} dock Place where panels are docked/undocked
  854   */
  855 Ext.ux.Accordion.DDDock = function(panel, group, dock) {
  856 
  857   // call parent constructor
  858   Ext.ux.Accordion.DDDock.superclass.constructor.call(this, panel.el.dom, group);
  859 
  860   // save panel and dock references for use in methods
  861   this.panel = panel;
  862   this.dock = dock;
  863 
  864   // drag by grabbing the title only
  865   this.setHandleElId(panel.titleEl.id);
  866 
  867   // move only in the dock if undockable
  868   if(false === dock.undockable) {
  869     this.setXConstraint(0, 0);
  870   }
  871 
  872   // init internal variables
  873   this.lastY = 0;
  874 
  875   //this.DDM.mode = Ext.dd.DDM.INTERSECT;
  876   this.DDM.mode = Ext.dd.DDM.POINT;
  877 
  878 }; // end of constructor
  879 // }}}
  880 
  881 // extend
  882 Ext.extend(Ext.ux.Accordion.DDDock, Ext.dd.DDProxy, {
  883 
  884   // {{{
  885   /**
  886     * Default DDProxy startDrag override
  887     * Saves some variable for use by other methods
  888     * and creates nice dragging proxy (ghost)
  889     *
  890     * Passed x, y arguments are not used
  891     */
  892   startDrag: function(x, y) {
  893 
  894     this.lastMoveTarget = null;
  895 
  896     // create nice dragging ghost
  897     this.createGhost();
  898 
  899     // get srcEl (the original) and dragEl (the ghost)
  900     var srcEl = Ext.get(this.getEl());
  901     var dragEl = Ext.get(this.getDragEl());
  902 
  903     // refresh constraints
  904     this.panel.constrainToDesktop();
  905     var dragHeight, rightC, bottomC;
  906     if(this.dock.undockable) {
  907       if(this.panel.collapsed) {
  908         dragHeight = this.panel.titleEl.getHeight();
  909       }
  910       else {
  911         dragHeight = dragEl.getHeight();
  912         dragHeight = dragHeight <= this.panel.titleEl.getHeight() ? srcEl.getHeight() : dragHeight;
  913       }
  914 
  915       rightC = this.rightConstraint + srcEl.getWidth() - dragEl.getWidth();
  916       bottomC = this.bottomConstraint + srcEl.getHeight() - dragHeight;
  917       this.setXConstraint(this.leftConstraint, rightC);
  918       this.setYConstraint(this.topConstraint, bottomC);
  919     }
  920     else {
  921       if(this.panel.docked) {
  922         this.setXConstraint(0, 0);
  923       }
  924     }
  925 
  926     // hide dragEl (will be shown by onDrag)
  927     dragEl.hide();
  928 
  929     // raise panel's "window" above others
  930     if(!this.panel.docked) {
  931       this.dock.raise(this.panel);
  932     }
  933 
  934     // hide panel's shadow if any
  935     this.panel.setShadow(false);
  936 
  937     // clear visibility of panel's body (was setup by animations)
  938     this.panel.body.dom.style.visibility = '';
  939 
  940     // hide source panel if undocked
  941     if(!this.panel.docked) {
  942       srcEl.hide();
  943       dragEl.show();
  944     }
  945 
  946   } // end of function startDrag
  947   // }}}
  948   // {{{
  949   /**
  950     * Called internally to create nice dragging proxy (ghost)
  951     */
  952   , createGhost: function() {
  953 
  954     // get variables
  955     var srcEl = Ext.get(this.getEl());
  956     var dragEl = Ext.get(this.getDragEl());
  957     var dock = this.dock;
  958     var panel = this.panel;
  959 
  960     // adjust look of ghost
  961     dragEl.addClass('x-dock-panel-ghost');
  962     dragEl.applyStyles({border:'1px solid #84a0c4','z-index': dock.zindex + dock.zindexInc});
  963 
  964     // set size of ghost same as original
  965     dragEl.setBox(srcEl.getBox());
  966     if(panel.docked) {
  967       if(panel.lastWidth && dock.undockable) {
  968         dragEl.setWidth(panel.lastWidth);
  969       }
  970       if(!panel.collapsed && dock.undockable && (panel.lastHeight > panel.titleEl.getHeight())) {
  971         dragEl.setHeight(panel.lastHeight);
  972       }
  973     }
  974 
  975     // remove unnecessary text nodes from srcEl
  976     srcEl.clean();
  977 
  978     // setup title
  979     var dragTitleEl = Ext.DomHelper.append(dragEl, {tag:'div'}, true);
  980     dragTitleEl.update(srcEl.dom.firstChild.innerHTML);
  981     dragTitleEl.dom.className = srcEl.dom.firstChild.className;
  982     if(panel.collapsed && Ext.isIE) {
  983       dragTitleEl.dom.style.borderBottom = "0";
  984     }
  985 
  986   } // end of function createGhost
  987   // }}}
  988   // {{{
  989   /**
  990     * Default DDProxy onDragOver override
  991     * It is called when dragging over a panel
  992     * or over the dock.body DropZone
  993     *
  994     * @param {Event} e not used
  995     * @param {String} targetId id of the target we're over
  996     *
  997     * Beware: While dragging over docked panels it's called
  998     * twice. Once for panel and once for DropZone
  999     */
 1000   , onDragOver: function(e, targetId) {
 1001 
 1002     this.currentTarget = targetId;
 1003 
 1004     // save targetId for use by endDrag
 1005     this.lastTarget = targetId;
 1006 
 1007     // get panel element
 1008     var srcEl = Ext.get(this.getEl());
 1009 
 1010     // get target panel
 1011     var targetPanel = this.dock.items.get(targetId);
 1012 
 1013     // landing indicators
 1014     if(targetPanel) {
 1015       if(targetPanel.docked && (targetPanel.collapsed || !this.panel.docked)) {
 1016         targetPanel.titleEl.addClass('x-dock-panel-title-dragover');
 1017       }
 1018     }
 1019     else {
 1020       if(!this.panel.docked) {
 1021         this.dock.body.addClass('x-dock-body-dragover');
 1022       }
 1023       else {
 1024         this.panel.titleEl.addClass('x-dock-panel-title-dragover');
 1025       }
 1026     }
 1027 
 1028     // do nothing else if we're not over another docked panel
 1029     if(!targetPanel || !targetPanel.docked) {
 1030       return;
 1031     }
 1032 
 1033     // reorder panels in dock if we're docked too
 1034     var targetEl;
 1035     if(this.panel.docked) {
 1036       targetEl = targetPanel.el;
 1037 
 1038       if(targetPanel.collapsed || this.lastMoveTarget !== targetPanel) {
 1039         if(this.movingUp) {
 1040           srcEl.insertBefore(targetEl);
 1041           this.lastMoveTarget = targetPanel;
 1042         }
 1043         else {
 1044           srcEl.insertAfter(targetEl);
 1045           this.lastMoveTarget = targetPanel;
 1046         }
 1047       }
 1048       this.DDM.refreshCache(this.groups);
 1049     }
 1050 
 1051   } // end of function onDragOver
 1052   // }}}
 1053   // {{{
 1054   /**
 1055     * Called internally when cursor leaves a drop target
 1056     * @param {Ext.Event} e
 1057     * @param {String} targetId id of target we're leaving
 1058     */
 1059   , onDragOut: function(e, targetId) {
 1060 
 1061     var targetPanel = this.dock.items.get(targetId);
 1062 
 1063     if(!targetPanel) {
 1064       this.dock.body.removeClass('x-dock-body-dragover');
 1065       if(this.dock.body.id === targetId) {
 1066         this.panel.titleEl.removeClass('x-dock-panel-title-dragover');
 1067       }
 1068     }
 1069     else {
 1070       targetPanel.titleEl.removeClass('x-dock-panel-title-dragover');
 1071     }
 1072     this.currentTarget = null;
 1073   }
 1074   // }}}
 1075   // {{{
 1076   /**
 1077     * Default DDProxy onDrag override
 1078     *
 1079     * It's called while dragging
 1080     * @param {Event} e used to get coordinates
 1081     */
 1082   , onDrag: function(e) {
 1083 
 1084     // get source (original) and proxy (ghost) elements
 1085     var srcEl = Ext.get(this.getEl());
 1086     var dragEl = Ext.get(this.getDragEl());
 1087 
 1088     if(!dragEl.isVisible()) {
 1089       dragEl.show();
 1090     }
 1091 
 1092     var y = e.getPageY();
 1093 
 1094     this.movingUp = this.lastY > y;
 1095     this.lastY = y;
 1096 
 1097   } // end of function onDrag
 1098   // }}}
 1099   // {{{
 1100   /**
 1101     * Default DDProxy endDrag override
 1102     *
 1103     * Called when dragging is finished
 1104     */
 1105   , endDrag: function() {
 1106 
 1107     // get the source (original) and proxy (ghost) elements
 1108     var srcEl = Ext.get(this.getEl());
 1109     var dragEl = Ext.get(this.getDragEl());
 1110 
 1111     // get box and hide the ghost
 1112     var box = dragEl.getBox();
 1113 
 1114     // remove any dragover classes from panel title and dock
 1115     this.panel.titleEl.removeClass('x-dock-panel-title-dragover');
 1116     this.dock.body.removeClass('x-dock-body-dragover');
 1117 
 1118     var targetPanel = this.dock.items.get(this.currentTarget);
 1119 
 1120     // undock (docked panel dropped out of dock)
 1121     if((this.panel.docked && !this.currentTarget) || (targetPanel && !targetPanel.docked)) {
 1122       this.dock.undock(this.panel, box);
 1123     }
 1124 
 1125     // dock (undocked panel dropped on dock)
 1126     else if(this.currentTarget) {
 1127       if(targetPanel) {
 1128         targetPanel.titleEl.removeClass('x-dock-panel-title-dragover');
 1129       }
 1130       else {
 1131         this.dock.body.removeClass('x-dock-body-dragover');
 1132       }
 1133       this.dock.dock(this.panel, this.currentTarget);
 1134     }
 1135 
 1136     // just free dragging
 1137     if(!this.panel.docked) {
 1138       this.panel.setBox(box);
 1139       srcEl.show();
 1140     }
 1141 
 1142     // clear the ghost content, hide id and move it off screen
 1143     dragEl.hide();
 1144     dragEl.update('');
 1145     dragEl.applyStyles({
 1146       top:'-9999px'
 1147       , left:'-9999px'
 1148       , height:'0px'
 1149       , width:'0px'
 1150     });
 1151 
 1152     // update order of docked panels
 1153     this.dock.updateOrder();
 1154 
 1155     // repair the expanded/collapsed states of panels in the dock
 1156     if(!this.panel.collapsed && !this.dock.independent && this.panel.docked) {
 1157       this.dock.collapseAll(false, this.panel);
 1158       this.dock.expanded = this.panel;
 1159     }
 1160 
 1161     // let the state manager know the new panel position
 1162     this.dock.fireEvent('panelbox', this.panel, {x:box.x, y:box.y, width:box.width, height:box.height});
 1163 
 1164   } // end of function endDrag
 1165   // }}}
 1166 
 1167 });
 1168 // }}}
 1169 // {{{
 1170 /**
 1171   * Private class for keeping and restoring state of the Accordion
 1172   */
 1173 Ext.ux.AccordionStateManager = function(dock) {
 1174   this.state = {
 1175     dock: {}
 1176     , panels: {}
 1177   };
 1178 };
 1179 
 1180 Ext.ux.AccordionStateManager.prototype = {
 1181 
 1182   // {{{
 1183   init: function(dock, provider) {
 1184     this.provider = provider;
 1185     var panel;
 1186     var state = provider.get(dock.id + '-dock-state');
 1187     if(state) {
 1188 
 1189       // {{{
 1190       // handle dock
 1191       if(undefined !== state.dock.independent) {
 1192         dock.setIndependent(state.dock.independent);
 1193       }
 1194 
 1195       if(undefined !== state.dock.undockable) {
 1196         dock.setUndockable(state.dock.undockable);
 1197       }
 1198 
 1199       if(undefined !== state.dock.useShadow) {
 1200         dock.setShadow(state.dock.useShadow);
 1201       }
 1202 
 1203       if('object' === typeof state.dock.order && state.dock.order.length) {
 1204         dock.setOrder(state.dock.order);
 1205       }
 1206       // }}}
 1207       // {{{
 1208       // handle panels
 1209       for(var panelId in state.panels) {
 1210         panel = dock.items.get(panelId);
 1211         if(panel) {
 1212           // {{{
 1213           // we need collapsed state early
 1214           panel.collapsed =
 1215             (undefined === typeof state.panels[panelId].collapsed)
 1216             ? true
 1217             : state.panels[panelId].collapsed
 1218           ;
 1219           // }}}
 1220           // {{{
 1221           // handle docked/undocked
 1222           if(undefined !== typeof state.panels[panelId].docked) {
 1223             if(!state.panels[panelId].docked && 'object' === typeof state.panels[panelId].box) {
 1224               dock.undock(panel, state.panels[panelId].box);
 1225             }
 1226             panel.docked = state.panels[panelId].docked;
 1227           }
 1228           // }}}
 1229           // {{{
 1230           // handle pinned state
 1231           if(undefined !== typeof state.panels[panelId].pinned) {
 1232             panel.pinned = state.panels[panelId].pinned;
 1233           }
 1234           // }}}
 1235           // {{{
 1236           // handle collapsed state
 1237           if(panel.collapsed) {
 1238             panel.collapse();
 1239           }
 1240           else {
 1241             if(!dock.expanded || dock.independent || panel.pinned || !panel.docked) {
 1242               panel.body.show();
 1243               panel.collapsed = false;
 1244               if(!panel.pinned && panel.docked) {
 1245                 dock.expanded = panel;
 1246               }
 1247               panel.updateVisuals();
 1248             }
 1249           }
 1250           // }}}
 1251 
 1252         }
 1253       }
 1254       // }}}
 1255 
 1256 //      dock.setSize(dock.body.getWidth(), dock.body.getHeight());
 1257       dock.setPanelHeight(dock.expanded);
 1258 
 1259       this.state = state;
 1260     }
 1261     this.dock = dock;
 1262 
 1263     // install event handlers on dock
 1264     dock.on({
 1265       panelcollapse: {scope: this, fn: this.onPanelCollapse}
 1266       , panelexpand: {scope: this, fn: this.onPanelCollapse}
 1267       , panelpinned: {scope: this, fn: this.onPanelPinned}
 1268       , independent: {scope: this, fn: this.onIndependent}
 1269       , orderchange: {scope: this, fn: this.onOrderChange}
 1270       , undockable: {scope: this, fn: this.onUndockable}
 1271       , paneldock: {scope: this, fn: this.onPanelUnDock}
 1272       , panelundock: {scope: this, fn: this.onPanelUnDock}
 1273       , panelbox: {scope: this, fn: this.onPanelUnDock}
 1274       , boxchange: {scope: this, fn: this.onPanelUnDock}
 1275       , useshadow: {scope: this, fn: this.onUseShadow}
 1276     });
 1277 
 1278   }
 1279   // }}}
 1280   // {{{
 1281   , onPanelCollapse: function(panel) {
 1282     this.state.panels[panel.id] = this.state.panels[panel.id] || {};
 1283     this.state.panels[panel.id].collapsed = panel.collapsed;
 1284     this.storeState();
 1285   }
 1286   // }}}
 1287   // {{{
 1288   , onPanelPinned: function(panel, pinned) {
 1289     this.state.panels[panel.id] = this.state.panels[panel.id] || {};
 1290     this.state.panels[panel.id].pinned = pinned;
 1291     this.storeState();
 1292   }
 1293   // }}}
 1294   // {{{
 1295   , onPanelUnDock: function(panel, box) {
 1296     this.state.panels[panel.id] = this.state.panels[panel.id] || {};
 1297     this.state.panels[panel.id].docked = panel.docked;
 1298     this.state.panels[panel.id].box = box || null;
 1299     this.storeState();
 1300   }
 1301   // }}}
 1302   // {{{
 1303   , onIndependent: function(dock, independent) {
 1304     this.state.dock.independent = independent;
 1305     this.storeState();
 1306   }
 1307   // }}}
 1308   // {{{
 1309   , onOrderChange: function(dock, order) {
 1310     this.state.dock.order = order;
 1311     this.storeState();
 1312   }
 1313   // }}}
 1314   // {{{
 1315   , onUndockable: function(dock, undockable) {
 1316     this.state.dock.undockable = undockable;
 1317     this.storeState();
 1318   }
 1319   // }}}
 1320   // {{{
 1321   , onUseShadow: function(dock, shadow) {
 1322     this.state.dock.useShadow = shadow;
 1323     this.storeState();
 1324   }
 1325   // }}}
 1326   // {{{
 1327   , storeState: function() {
 1328     this.provider.set.defer(700, this, [this.dock.id + '-dock-state', this.state]);
 1329   }
 1330   // }}}
 1331 
 1332 }; // end of Ext.ux.AccordionStateManager.prototype
 1333 // }}}
 1334 
 1335 // end of file