Ext.ux.Accordion 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.Accordion Extension Class
    8   *
    9   * @author  Ing. Jozef Sakalos
   10   * @version $Id: Ext.ux.Accordion.js 152 2007-08-21 17:46:03Z 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} forceOrder set to true if to disable reordering of panels (defaults to false)
   22   * @cfg {Boolean} independent true to make panels independent (defaults to false)
   23   * @cfg {Integer} initialHeight Initial height to set box to (defaults to 0)
   24   * @cfg {Boolean} keepState Set to false to exclude this accordion from state management (defaults to true)
   25   * @cfg {Boolean} monitorWindowResize if true panels are moved to
   26   *  viewport if window is small (defaults to true)
   27   * @cfg {Boolean} resizable global resizable flag for all panels (defaults to true)
   28   * @cfg {Boolean} undockable true to allow undocking of panels (defaults to true)
   29   * @cfg {Boolean} useShadow global useShadow flag for all panels. (defaults to true)
   30   * @cfg {Element/HTMLElement/String} wrapEl Element to wrap with nice surrounding
   31   */
   32 Ext.ux.Accordion = function(el, config) {
   33 
   34   // call parent constructor
   35   Ext.ux.Accordion.superclass.constructor.call(this, el, config);
   36 
   37   // create collection for panels
   38   this.items = new Ext.util.MixedCollection();
   39 
   40   // assume no panel is expanded
   41   this.expanded = null;
   42 
   43   // {{{
   44   // install event handlers
   45   this.on({
   46 
   47     // {{{
   48     // runs before expansion. Triggered by panel's beforeexpand event
   49     beforeexpand: {
   50         scope: this
   51       , fn: function(panel) {
   52           // raise panel above others
   53           if(!panel.docked) {
   54             this.raise(panel);
   55           }
   56 
   57           // set fixed height
   58           panel.autoSize();
   59 //          var panelBodyHeight;
   60 //          if(this.fitHeight && panel.docked) {
   61 //            panelBodyHeight = this.getPanelBodyHeight();
   62 //            if(panelBodyHeight) {
   63 //              panel.body.setHeight(panelBodyHeight);
   64 //            }
   65 //          }
   66 
   67           if(panel.docked) {
   68             this.expandCount++;
   69             this.expanding = true;
   70 //            this.setDockScroll(false);
   71           }
   72 
   73           // don't collapse others if independent or not docked
   74           if(this.independent || !panel.docked) {
   75             return this;
   76           }
   77 
   78           // collapse expanded panel
   79           if(this.expanded && this.expanded.docked) {
   80             this.expanded.collapse();
   81           }
   82 
   83           // remember this panel as expanded
   84           this.expanded = panel;
   85     }}
   86     // }}}
   87     // {{{
   88     // runs before panel collapses. Triggered by panel's beforecollapse event
   89     , beforecollapse: {
   90         scope: this
   91       , fn: function(panel) {
   92 
   93         // raise panel if not docked
   94         if(!panel.docked) {
   95           this.raise(panel);
   96         }
   97         return this;
   98     }}
   99     // }}}
  100     // {{{
  101     // runs on when panel expands (before animation). Triggered by panel's expand event
  102     , expand: {
  103         scope: this
  104       , fn: function(panel) {
  105         if(this.hideOtherOnExpand) {
  106           this.hideOther(panel);
  107         }
  108         this.fireEvent('panelexpand', panel);
  109     }}
  110     // }}}
  111     // {{{
  112     // runs on when panel collapses (before animation). Triggered by panel's collapse event
  113     , collapse: {
  114         scope: this
  115       , fn: function(panel) {
  116         this.fireEvent('panelcollapse', panel);
  117     }}
  118     // }}}
  119     // {{{
  120     // runs on when animation is completed. Triggered by panel's animationcompleted event
  121     , animationcompleted: {
  122       scope: this
  123       , fn: function(panel) {
  124         var box = panel.el.getBox();
  125         this.expandCount = (this.expandCount && this.expanding) ? --this.expandCount : 0;
  126         if((0 === this.expandCount) && this.expanding) {
  127 //          this.setDockScroll(true);
  128           this.expanding = false;
  129         }
  130         if(this.hideOtherOnExpand) {
  131           if(panel.collapsed && panel.docked) {
  132             this.showOther(panel);
  133           }
  134 //          else if(panel.docked) {
  135 //            this.hideOther(panel);
  136 //          }
  137         }
  138 //        this.fireEvent('panelbox', panel, box);
  139     }}
  140     // }}}
  141     // {{{
  142     // runs when panel is pinned. Triggered by panel's pinned event
  143     , pinned: {
  144         scope: this
  145       , fn: function(panel, pinned) {
  146         if(!pinned) {
  147           if(panel.collapseOnUnpin) {
  148             panel.collapse();
  149           }
  150           else if(!this.independent) {
  151             this.items.each(function(p) {
  152               if(p !== panel && p.docked && !p.pinned) {
  153                 p.collapse();
  154               }
  155             });
  156             this.expanded = panel;
  157           }
  158         }
  159         if(this.hideOtherOnExpand) {
  160           if(panel.docked && pinned) {
  161             this.showOther(panel);
  162           }
  163         }
  164         this.fireEvent('panelpinned', panel, pinned);
  165     }}
  166     // }}}
  167 
  168     , destroy: {
  169       scope:this
  170       , fn: function(panel) {
  171         this.items.removeKey(panel.id);
  172         this.updateOrder();
  173     }}
  174   });
  175   // }}}
  176   // {{{
  177   // add events
  178   this.addEvents({
  179     /**
  180       * Fires when a panel of the dock is collapsed
  181       * @event panelcollapse
  182       * @param {Ext.ux.InfoPanel} panel
  183       */
  184     panelcollapse: true
  185 
  186     /**
  187       * Fires when a panel of the dock is expanded
  188       * @event panelexpand
  189       * @param {Ext.ux.InfoPanel} panel
  190       */
  191     , panelexpand: true
  192 
  193     /**
  194       * Fires when a panel of the dock is pinned
  195       * @event panelpinned
  196       * @param {Ext.ux.InfoPanel} panel
  197       * @param {Boolean} pinned true if panel was pinned false if unpinned
  198       */
  199     , panelpinned: true
  200 
  201     /**
  202       * Fires when the independent state of dock changes
  203       * @event independent
  204       * @param {Ext.ux.Accordion} this
  205       * @param {Boolean} independent New independent state
  206       */
  207     , independent: true
  208 
  209     /**
  210       * Fires when the order of panel is changed
  211       * @event orderchange
  212       * @param {Ext.ux.Accordion} this
  213       * @param {Array} order New order array
  214       */
  215     , orderchange: true
  216 
  217     /**
  218       * Fires when the undockable state of dock changes
  219       * @event undockable
  220       * @param {Ext.ux.Accordion} this
  221       * @param {Array} undockable New undockable state
  222       */
  223     , undockable: true
  224 
  225     /**
  226       * Fires when a panel is undocked
  227       * @event panelundock
  228       * @param {Ext.ux.InfoPanel} panel
  229       * @param {Object} box Position and size object
  230       */
  231     , panelundock: true
  232 
  233     /**
  234       * Fires when a panel is undocked
  235       * @event paneldock
  236       * @param {Ext.ux.InfoPanel} panel
  237       */
  238     , paneldock: true
  239 
  240     /**
  241       * Fires when a panel box is changed, e.g. after dragging
  242       * @event panelbox
  243       * @param {Ext.ux.InfoPanel} panel
  244       * @param {Object} box Position and size object
  245       */
  246     , panelbox: true
  247 
  248     /**
  249       * Fires when useShadow status changes
  250       * @event useshadow
  251       * @param {Ext.ux.Accordion} this
  252       * @param {Boolean} shadow Use shadow (for undocked panels) flag
  253       */
  254     , useshadow: true
  255 
  256     /**
  257       * Fires before the panel is detached from this accordion. Return false to cancel the detach
  258       * @event beforedetach
  259       * @param {Ext.ux.Accordion} this
  260       * @param {Ext.ux.InfoPanel} panel being detached
  261       */
  262     , beforedetach: true
  263 
  264     /**
  265       * Fires after the panel has been detached from this accordion
  266       * @event detach
  267       * @param {Ext.ux.Accordion} this
  268       * @param {Ext.ux.InfoPanel} panel detached panel
  269       */
  270     , detach: true
  271 
  272     /**
  273       * Fires before the panel is attached from this accordion. Return false to cancel the attach
  274       * @event beforeattach
  275       * @param {Ext.ux.Accordion} this
  276       * @param {Ext.ux.InfoPanel} panel being attached
  277       */
  278     , beforeattach: true
  279 
  280     /**
  281       * Fires after the panel is attached to this accordion
  282       * @event attach
  283       * @param {Ext.ux.Accordion} this
  284       * @param {Ext.ux.InfoPanel} panel attached panel
  285       */
  286     , attach: true
  287 
  288   });
  289   // }}}
  290 
  291   // setup body
  292   this.body = Ext.get(this.body) || this.el;
  293   this.resizeEl = this.body;
  294   this.id = this.id || this.body.id;
  295   this.body.addClass('x-dock-body');
  296 
  297   // setup desktop
  298   this.desktop = Ext.get(this.desktop || document.body);
  299   //this.desktop = this.desktop.dom || this.desktop;
  300 
  301   // setup fixed hight
  302   this.wrapEl = Ext.get(this.wrapEl);
  303   if(this.fitHeight) {
  304     this.body.setStyle('overflow', 'hidden');
  305 //    this.bodyHeight = this.initialHeight || this.body.getHeight();
  306     this.body.setHeight(this.initialHeight || this.body.getHeight());
  307     if(this.boxWrap && this.wrapEl) {
  308       this.wrapEl.boxWrap();
  309     }
  310   }
  311 
  312   // watch window resize
  313   if(this.monitorWindowResize) {
  314     Ext.EventManager.onWindowResize(this.adjustViewport, this);
  315   }
  316 
  317   // create drop zone for panels
  318   this.dd = new Ext.dd.DropZone(this.body.dom, {
  319     ddGroup: this.ddGroup || 'dock-' + this.id
  320   });
  321 
  322   Ext.ux.AccordionManager.add(this);
  323 
  324 }; // end of constructor
  325 
  326 // extend
  327 Ext.extend(Ext.ux.Accordion, Ext.ContentPanel, {
  328 
  329   // {{{
  330   // defaults
  331   independent: false
  332   , undockable: true
  333   , useShadow: true
  334   , boxWrap: false
  335   , fitHeight: false
  336   , initialHeight: 0
  337   , animate: true // global animation flag
  338   , expandCount: 0
  339   , expanding: false
  340   , monitorWindowResize: true
  341   , resizable: true // global resizable flag
  342   , draggable: true // global draggable flag
  343   , forceOrder: false
  344   , keepState: true
  345   , hideOtherOnExpand: false
  346   // }}}
  347   // {{{
  348   /**
  349     * Adds the panel to Accordion
  350     * @param {Ext.ux.InfoPanel} panel Panel to add
  351     * @return {Ext.ux.InfoPanel} added panel
  352     */
  353   , add: function(panel) {
  354 
  355     // append panel to body
  356     this.body.appendChild(panel.el);
  357 
  358     this.attach(panel);
  359 
  360     // panel dragging
  361     if(undefined === panel.draggable && this.draggable) {
  362       panel.draggable = true;
  363       panel.dd = new Ext.ux.Accordion.DDDock(panel, this.ddGroup || 'dock-' + this.id, this);
  364     }
  365 
  366     // panel resizing
  367     if(undefined === panel.resizable && this.resizable) {
  368       panel.resizable = true;
  369 //      panel.setResizable(true);
  370     }
  371 
  372     // panel shadow
  373     panel.useShadow = undefined === panel.useShadow ? this.useShadow : panel.useShadow;
  374     panel.setShadow(panel.useShadow);
  375     if(panel.shadow) {
  376       panel.shadow.hide();
  377     }
  378 
  379     // panel animation
  380     panel.animate = undefined === panel.animate ? this.animate : panel.animate;
  381 
  382     // z-index for panel
  383     panel.zindex = Ext.ux.AccordionManager.getNextZindex();
  384 
  385     panel.docked = true;
  386     panel.desktop = this.desktop;
  387 
  388     if(false === panel.collapsed) {
  389       panel.collapsed = true;
  390       panel.expand(true);
  391     }
  392     return panel;
  393 
  394   }
  395   // }}}
  396   // {{{
  397   /**
  398     * attach panel to this accordion
  399     * @param {Ext.ux.InfoPanel} panel panel to attach
  400     * @return {Ext.ux.Accordion} this
  401     */
  402   , attach: function(panel) {
  403 
  404     // fire beforeattach event
  405     if(false === this.fireEvent('beforeattach', this, panel)) {
  406       return this;
  407     }
  408 
  409     // add panel to items
  410     this.items.add(panel.id, panel);
  411 
  412     // install event handlers
  413     this.installRelays(panel);
  414     panel.bodyClickDelegate = this.onClickPanelBody.createDelegate(this, [panel]);
  415     panel.body.on('click', panel.bodyClickDelegate);
  416 
  417     // set panel dock
  418     panel.dock = this;
  419 
  420     // add docked class to panel body
  421     panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
  422 
  423     // repair panel height
  424     panel.autoSize();
  425     if(this.fitHeight) {
  426       this.setPanelHeight(panel);
  427     }
  428 
  429     // fire attach event
  430     this.fireEvent('attach', this, panel);
  431 
  432     return this;
  433   }
  434   // }}}
  435   // {{{
  436   /**
  437     * detach panel from this accordion
  438     * @param {Ext.ux.InfoPanel} panel to detach
  439     * @return {Ext.ux.Accordion} this
  440     */
  441   , detach: function(panel) {
  442 
  443     // fire beforedetach event
  444     if(false === this.fireEvent('beforedetach', this, panel)) {
  445       return this;
  446     }
  447 
  448     // unhook events from panel
  449     this.removeRelays(panel);
  450     panel.body.un('click', panel.bodyClickDelegate);
  451 
  452     // remove panel from items
  453     this.items.remove(panel);
  454     panel.dock = null;
  455 
  456     // remove docked class from panel body
  457     panel.body.replaceClass('x-dock-panel-body-docked', 'x-dock-panel-body-undocked');
  458 
  459     // repair expanded property
  460     if(this.expanded === panel) {
  461       this.expanded = null;
  462     }
  463 
  464     // repair panel height
  465     panel.autoSize();
  466     if(this.fitHeight) {
  467       this.setPanelHeight();
  468     }
  469 
  470     // fire detach event
  471     this.fireEvent('detach', this, panel);
  472 
  473     return this;
  474   }
  475   // }}}
  476   // {{{
  477   /**
  478     * Called internally to raise panel above others
  479     * @param {Ext.ux.InfoPanel} panel Panel to raise
  480     * @return {Ext.ux.InfoPanel} panel Panel that has been raised
  481     */
  482   , raise: function(panel) {
  483     return Ext.ux.AccordionManager.raise(panel);
  484   }
  485   // }}}
  486   // {{{
  487   /**
  488     * Resets the order of panels within the dock
  489     *
  490     * @return {Ext.ux.Accordion} this
  491     */
  492   , resetOrder: function() {
  493     this.items.each(function(panel) {
  494       if(!panel.docked) {
  495         return;
  496       }
  497       this.body.appendChild(panel.el);
  498     }, this);
  499     this.updateOrder();
  500     return this;
  501   }
  502   // }}}
  503   // {{{
  504   /**
  505     * Called internally to update the order variable after dragging
  506     */
  507   , updateOrder: function() {
  508     var order = [];
  509     var titles = this.body.select('div.x-layout-panel-hd');
  510     titles.each(function(titleEl) {
  511       order.push(titleEl.dom.parentNode.id);
  512     });
  513     this.order = order;
  514     this.fireEvent('orderchange', this, order);
  515   }
  516   // }}}
  517   // {{{
  518   /**
  519     * Returns array of panel ids in the current order
  520     * @return {Array} order of panels
  521     */
  522   , getOrder: function() {
  523     return this.order;
  524   }
  525   // }}}
  526   // {{{
  527   /**
  528     * Set the order of panels
  529     * @param {Array} order Array of ids of panels in required order.
  530     * @return {Ext.ux.Accordion} this
  531     */
  532   , setOrder: function(order) {
  533     if('object' !== typeof order || undefined === order.length) {
  534       throw "setOrder: Argument is not array.";
  535     }
  536     var panelEl, dock, panelId, panel;
  537     for(var i = 0; i < order.length; i++) {
  538       panelId = order[i];
  539       dock = Ext.ux.AccordionManager.get(panelId);
  540       if(dock && dock !== this) {
  541         panel = dock.items.get(panelId);
  542         dock.detach(panel);
  543         this.attach(panel);
  544       }
  545       panelEl = Ext.get(panelId);
  546       if(panelEl) {
  547         this.body.appendChild(panelEl);
  548       }
  549     }
  550     this.updateOrder();
  551     return this;
  552   }
  553   // }}}
  554   // {{{
  555   /**
  556     * Collapse all docked panels
  557     * @param {Boolean} alsoPinned true to first unpin then collapse
  558     * @param {Ext.ux.InfoPanel} except This panel will not be collapsed.
  559     * @return {Ext.ux.Accordion} this
  560     */
  561   , collapseAll: function(alsoPinned, except) {
  562     this.items.each(function(panel) {
  563       if(panel.docked) {
  564         panel.pinned = alsoPinned ? false : panel.pinned;
  565         if(!except || panel !== except) {
  566           panel.collapse();
  567         }
  568       }
  569     }, this);
  570     return this;
  571   }
  572   // }}}
  573   // {{{
  574   /**
  575     * Expand all docked panels in independent mode
  576     * @return {Ext.ux.Accordion} this
  577     */
  578   , expandAll: function() {
  579     if(this.independent) {
  580       this.items.each(function(panel) {
  581         if(panel.docked && panel.collapsed) {
  582           panel.expand();
  583         }
  584       }, this);
  585     }
  586   }
  587   // }}}
  588   // {{{
  589   /**
  590     * Called internally while dragging and by state manager
  591     * @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
  592     * @box {Object} box coordinates with target position and size
  593     * @return {Ext.ux.Accordion} this
  594     */
  595   , undock: function(panel, box) {
  596 
  597     // get panel if necessary
  598     panel = 'string' === typeof panel ? this.items.get(panel) : panel;
  599 
  600     // proceed only if we have docked panel and in undockable mode
  601     if(panel && panel.docked && this.undockable) {
  602 
  603       // sanity check
  604       if(box.x < 0 || box.y < 0) {
  605         return this;
  606       }
  607 
  608       // todo: check this
  609 //      if(panel.collapsed) {
  610 //        box.height = panel.lastHeight || panel.maxHeight || box.height;
  611 //      }
  612 
  613       // move the panel in the dom (append to desktop)
  614       this.desktop.appendChild(panel.el.dom);
  615 
  616       // adjust panel visuals
  617       panel.el.applyStyles({
  618         position:'absolute'
  619         , 'z-index': panel.zindex
  620       });
  621       panel.body.replaceClass('x-dock-panel-body-docked', 'x-dock-panel-body-undocked');
  622 
  623       // position the panel
  624       panel.setBox(box);
  625 
  626       // reset docked flag
  627       panel.docked = false;
  628 
  629       // hide panel shadow (will be shown by raise)
  630       if(panel.shadow) {
  631         panel.shadow.hide();
  632       }
  633 
  634       // raise panel above others
  635       this.raise(panel);
  636 
  637       panel.autoSize();
  638 
  639       if(panel === this.expanded) {
  640         this.expanded = null;
  641       }
  642 
  643       // set the height of a docked expanded panel
  644       this.setPanelHeight(this.expanded);
  645 
  646       // enable resizing and scrolling
  647       panel.setResizable(!panel.collapsed);
  648 
  649       // remember size of the undocked panel
  650       panel.lastWidth = box.width;
  651 //      panel.lastHeight = box.height;
  652 
  653       // fire panelundock event
  654       this.fireEvent('panelundock', panel, {x:box.x, y:box.y, width:box.width, height:box.height});
  655 
  656 //      this.updateOrder();
  657     }
  658 
  659     return this;
  660   }
  661   // }}}
  662   // {{{
  663   /**
  664     * Called internally while dragging
  665     * @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
  666     * @param {String} targetId id of panel after which this panel will be docked
  667     * @return {Ext.ux.Accordion} this
  668     */
  669   , dock: function(panel, targetId) {
  670 
  671     // get panel if necessary
  672     panel = 'string' === typeof panel ? this.items.get(panel) : panel;
  673 
  674     // proceed only if we have a docked panel
  675     var dockWidth, newTargetId, panelHeight, idx, i, targetPanel;
  676     if(panel && !panel.docked) {
  677 
  678       // find correct target if order is forced
  679       if(this.forceOrder) {
  680         idx = this.items.indexOf(panel);
  681         for(i = idx + 1; i < this.items.getCount(); i++) {
  682           targetPanel = this.items.itemAt(i);
  683           if(targetPanel.docked) {
  684             newTargetId = targetPanel.id;
  685             break;
  686           }
  687         }
  688         targetId = newTargetId || this.id;
  689       }
  690 
  691       // remember width and height
  692       if(!panel.collapsed) {
  693 //        panel.lastWidth = panel.el.getWidth();
  694 //        panel.lastHeight = panel.el.getHeight();
  695         if(!this.independent && this.expanded) {
  696           this.expanded.collapse();
  697         }
  698         this.expanded = panel;
  699       }
  700 
  701       dockWidth = this.body.getWidth(true);
  702 
  703       // move the panel element in the dom
  704       if(targetId && (this.body.id !== targetId)) {
  705         panel.el.insertBefore(Ext.fly(targetId));
  706       }
  707       else {
  708         panel.el.appendTo(this.body);
  709       }
  710 
  711       // set docked flag
  712       panel.docked = true;
  713 
  714       // adjust panel visuals
  715       panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
  716       panel.el.applyStyles({
  717         top:''
  718         , left:''
  719         , width:''
  720 //        , height:''
  721         , 'z-index':''
  722         , position:'relative'
  723         , visibility:''
  724       });
  725       panel.body.applyStyles({width: Ext.isIE ? dockWidth + 'px' : '', height:''});
  726 
  727       panel.autoSize();
  728 //      if(!this.fitHeight) {
  729 //        panelHeight = panel.fixedHeight || panel.maxHeight;
  730 //        if(panelHeight) {
  731 //          panel.setHeight(panelHeight);
  732 //        }
  733 //      }
  734 
  735       // disable resizing and shadow
  736       panel.setResizable(false);
  737       if(panel.shadow) {
  738         panel.shadow.hide();
  739       }
  740 
  741       // set panel height (only if this.fitHeight = true)
  742       this.setPanelHeight(panel.collapsed ? this.expanded : panel);
  743 
  744       // fire paneldock event
  745       this.fireEvent('paneldock', panel);
  746 
  747 //      this.updateOrder();
  748     }
  749 
  750     return this;
  751   }
  752   // }}}
  753   // {{{
  754   /**
  755     * Moves panel from this dock (accordion) to another
  756     * @param {Ext.ux.InfoPanel} panel Panel to move
  757     * @param {Ext.ux.Accordion} targetDock Dock to move to
  758     */
  759   , moveToDock: function(panel, targetDock) {
  760     this.detach(panel);
  761     targetDock.attach(panel);
  762     panel.docked = false;
  763     targetDock.dock(panel);
  764     this.setPanelHeight();
  765     this.updateOrder();
  766     targetDock.updateOrder();
  767   }
  768   // }}}
  769   // {{{
  770   /**
  771     * Sets the independent mode
  772     * @param {Boolean} independent set to false for normal mode
  773     * @return {Ext.ux.Accordion} this
  774     */
  775   , setIndependent: function(independent) {
  776     this.independent = independent ? true : false;
  777     this.fireEvent('independent', this, independent);
  778     return this;
  779   }
  780   // }}}
  781   // {{{
  782   /**
  783     * Sets the undockable mode
  784     * If undockable === true all undocked panels are docked and collapsed (except pinned)
  785     * @param {Boolean} undockable set to true to not allow undocking
  786     * @return {Ext.ux.Accordion} this
  787     */
  788   , setUndockable: function(undockable) {
  789     this.items.each(function(panel) {
  790 
  791       // dock and collapse (except pinned) all undocked panels if not undockable
  792       if(!undockable && !panel.docked) {
  793         this.dock(panel);
  794         if(!this.independent && !panel.collapsed && !panel.pinned) {
  795           panel.collapse();
  796         }
  797       }
  798 
  799       // refresh dragging constraints
  800       if(panel.docked && panel.draggable) {
  801         panel.dd.constrainTo(this.body, 0, false);
  802         panel.dd.clearConstraints();
  803         if(undockable) {
  804           panel.constrainToDesktop();
  805         }
  806         else {
  807           panel.dd.setXConstraint(0,0);
  808         }
  809       }
  810     }, this);
  811 
  812     // set the flag and fire event
  813     this.undockable = undockable;
  814     this.fireEvent('undockable', this, undockable);
  815     return this;
  816   }
  817   // }}}
  818   // {{{
  819   /**
  820     * Sets the shadows for all panels
  821     * @param {Boolean} shadow set to false to disable shadows
  822     * @return {Ext.ux.Accordion} this
  823     */
  824   , setShadow: function(shadow) {
  825     this.items.each(function(panel) {
  826       panel.useShadow = shadow;
  827       panel.setShadow(false);
  828       if(!panel.docked) {
  829         panel.setShadow(shadow);
  830       }
  831     });
  832     this.useShadow = shadow;
  833     this.fireEvent('useshadow', this, shadow);
  834     return this;
  835   }
  836   // }}}
  837 // {{{
  838   /**
  839     * Called when user clicks the panel body
  840     * @param {Ext.ux.InfoPanel} panel
  841     */
  842   , onClickPanelBody: function(panel) {
  843     if(!panel.docked) {
  844       this.raise(panel);
  845     }
  846   }
  847   // }}}
  848   // {{{
  849   /**
  850     * Called internally for fixed height docks to get current height of panel(s)
  851     */
  852   , getPanelBodyHeight: function() {
  853       var titleHeight = 0;
  854       this.items.each(function(panel) {
  855         titleHeight += panel.docked ? panel.titleEl.getHeight() : 0;
  856       });
  857       this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb') + 1;
  858 //      this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb');
  859       return this.panelBodyHeight;
  860   }
  861   // }}}
  862   // {{{
  863   /**
  864     * Sets the height of panel body
  865     * Used with fixed height (fitHeight:true) docs
  866     * @param {Ext.ux.InfoPanel} panel (defaults to this.expanded)
  867     * @return {Ext.ux.Accordion} this
  868     */
  869   , setPanelHeight: function(panel) {
  870     panel = panel || this.expanded;
  871     if(this.fitHeight && panel && panel.docked) {
  872       panel.body.setHeight(this.getPanelBodyHeight());
  873       panel.setHeight(panel.getHeight());
  874     }
  875     return this;
  876   }
  877   // }}}
  878   // {{{
  879   /**
  880     * Constrains the dragging of panels do the desktop
  881     * @return {Ext.ux.Accordion} this
  882     */
  883   , constrainToDesktop: function() {
  884     this.items.each(function(panel) {
  885       panel.constrainToDesktop();
  886     }, this);
  887     return this;
  888   }
  889   // }}}
  890   // {{{
  891   /**
  892     * Clears dragging constraints of panels
  893     * @return {Ext.ux.Accordion} this
  894     */
  895   , clearConstraints: function() {
  896     this.items.each(function(panel) {
  897       panel.dd.clearConstraints();
  898     });
  899   }
  900   // }}}
  901   // {{{
  902   /**
  903     * Shows all panels
  904     * @param {Boolean} show (optional) if false hides the panels instead of showing
  905     * @param {Boolean} alsoUndocked show also undocked panels (defaults to false)
  906     * @return {Ext.ux.Accordion} this
  907     */
  908   , showAll: function(show, alsoUndocked) {
  909     show = (false === show ? false : true);
  910     this.items.each(function(panel) {
  911       panel.show(show, alsoUndocked);
  912     });
  913     return this;
  914   }
  915   // }}}
  916 
  917   , showOther: function(panel, show, alsoPinned) {
  918     show = (false === show ? false : true);
  919     this.items.each(function(p) {
  920         if(p === panel || (p.pinned && !alsoPinned)) {
  921           return;
  922         }
  923         if(show) {
  924           p.show();
  925         }
  926         else {
  927           p.hide();
  928         }
  929     });
  930   }
  931 
  932   , hideOther: function(panel, alsoPinned) {
  933     this.showOther(panel, false, alsoPinned);
  934   }
  935 
  936   // {{{
  937   /**
  938     * Hides all panels
  939     * @param {Boolean} alsoUndocked hide also undocked panels (defaults to false)
  940     * @return {Ext.ux.Accordion} this
  941     */
  942   , hideAll: function(alsoUndocked) {
  943     return this.showAll(false, alsoUndocked);
  944   }
  945   // }}}
  946   // {{{
  947   /**
  948     * Called internally to disable/enable scrolling of the dock while animating
  949     * @param {Boolean} enable true to enable, false to disable
  950     * @return {void}
  951     * @todo not used at present - revise
  952     */
  953   , setDockScroll: function(enable) {
  954     if(enable && !this.fitHeight) {
  955       this.body.setStyle('overflow','auto');
  956     }
  957     else {
  958       this.body.setStyle('overflow','hidden');
  959     }
  960   }
  961   // }}}
  962   // {{{
  963   /**
  964     * Set Accordion size
  965     * Overrides ContentPanel.setSize
  966     *
  967     * @param {Integer} w width
  968     * @param {Integer} h height
  969     * @return {Ext.ux.Accordion} this
  970     */
  971   , setSize: function(w, h) {
  972     // call parent's setSize
  973     Ext.ux.Accordion.superclass.setSize.call(this, w, h);
  974 //    this.body.setHeight(h);
  975     this.setPanelHeight();
  976 
  977     return this;
  978   }
  979   // }}}
  980   // {{{
  981   /**
  982     * Called as windowResize event handler
  983     *
  984     * @todo: review
  985     */
  986   , adjustViewport: function() {
  987     var viewport = this.desktop.dom === document.body ? {} : Ext.get(this.desktop).getBox();
  988 
  989     viewport.height =
  990       this.desktop === document.body
  991       ? window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
  992       : viewport.height
  993     ;
  994 
  995     viewport.width =
  996       this.desktop === document.body
  997       ? window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
  998       : viewport.width
  999     ;
 1000 
 1001     viewport.x = this.desktop === document.body ? 0 : viewport.x;
 1002     viewport.y = this.desktop === document.body ? 0 : viewport.y;
 1003 
 1004     this.items.each(function(panel) {
 1005       if(!panel.docked) {
 1006         panel.moveToViewport(viewport);
 1007       }
 1008     });
 1009 
 1010   }
 1011   // }}}
 1012   // {{{
 1013   /**
 1014     * private - called internally to create relay event function
 1015     * @param {String} ename event name to relay
 1016     * @return {Function} relay event function
 1017     */
 1018   , createRelay: function(ename) {
 1019     return function() {
 1020       return this.fireEvent.apply(this, Ext.combine(ename, Array.prototype.slice.call(arguments, 0)));
 1021     };
 1022   }
 1023   // }}}
 1024   // {{{
 1025   /**
 1026     * Array of event names to relay
 1027     */
 1028   , relayedEvents: [
 1029     'beforecollapse'
 1030     , 'collapse'
 1031     , 'beforeexpand'
 1032     , 'expand'
 1033     , 'animationcompleted'
 1034     , 'pinned'
 1035     , 'boxchange'
 1036     , 'destroy'
 1037   ]
 1038   // }}}
 1039   // {{{
 1040   /**
 1041     * private - called internaly to install event relays on panel
 1042     * @param {Ext.ux.InfoPanel} panel panel to install events on
 1043     */
 1044   , installRelays: function(panel) {
 1045     panel.relays = {};
 1046     var ename, fn;
 1047     for(var i = 0; i < this.relayedEvents.length; i++) {
 1048       ename = this.relayedEvents[i];
 1049       fn = this.createRelay(ename);
 1050       panel.relays[ename] = fn;
 1051       panel.on(ename, fn, this);
 1052     }
 1053   }
 1054   // }}}
 1055   // {{{
 1056   /**
 1057     * private - called internaly to remove installed relays
 1058     * @param {Ext.ux.InfoPanel} panel panel to remove relays from
 1059     */
 1060   , removeRelays: function(panel) {
 1061     for(var ename in panel.relays) {
 1062       panel.un(ename, panel.relays[ename], this);
 1063     }
 1064     panel.relays = {};
 1065   }
 1066   // }}}
 1067   // {{{
 1068   /**
 1069     * Removes and destroys panel
 1070     * @param {String/InfoPanel} panel Panel object or id
 1071     */
 1072   , remove: function(panel) {
 1073     panel = this.items.get(panel.id || panel);
 1074     if(panel) {
 1075       this.detach(panel);
 1076       panel.destroy();
 1077     }
 1078   }
 1079   // }}}
 1080   // {{{
 1081   /**
 1082     * Removes and destroys all panels
 1083     */
 1084   , removeAll: function() {
 1085     this.items.each(function(panel) {
 1086         this.remove(panel);
 1087     }, this);
 1088   }
 1089   // }}}
 1090   // {{{
 1091   /**
 1092     * Destroys Accrodion
 1093     */
 1094   , destroy: function() {
 1095     this.removeAll();
 1096     Ext.ux.Accordion.superclass.destroy.call(this);
 1097   }
 1098   // }}}
 1099 
 1100 }); // end of extend
 1101 
 1102 // {{{
 1103 // {{{
 1104 /**
 1105   * @class Ext.ux.Accordion.DDDock
 1106   * @constructor
 1107   * @extends Ext.dd.DDProxy
 1108   * @param {Ext.ux.InfoPanel} panel Panel the dragging object is created for
 1109   * @param {String} group Only elements of same group interact
 1110   * @param {Ext.ux.Accordion} dock Place where panels are docked/undocked
 1111   */
 1112 Ext.ux.Accordion.DDDock = function(panel, group, dock) {
 1113 
 1114   // call parent constructor
 1115   Ext.ux.Accordion.DDDock.superclass.constructor.call(this, panel.el.dom, group);
 1116 
 1117   // save panel and dock references for use in methods
 1118   this.panel = panel;
 1119   this.dock = dock;
 1120 
 1121   // drag by grabbing the title only
 1122   this.setHandleElId(panel.titleEl.id);
 1123 
 1124   // move only in the dock if undockable
 1125   if(false === dock.undockable) {
 1126     this.setXConstraint(0, 0);
 1127   }
 1128 
 1129   // init internal variables
 1130   this.lastY = 0;
 1131 
 1132   //this.DDM.mode = Ext.dd.DDM.INTERSECT;
 1133   this.DDM.mode = Ext.dd.DDM.POINT;
 1134 
 1135 }; // end of constructor
 1136 // }}}
 1137 
 1138 // extend
 1139 Ext.extend(Ext.ux.Accordion.DDDock, Ext.dd.DDProxy, {
 1140 
 1141   // {{{
 1142   /**
 1143     * Default DDProxy startDrag override
 1144     * Saves some variable for use by other methods
 1145     * and creates nice dragging proxy (ghost)
 1146     *
 1147     * Passed x, y arguments are not used
 1148     */
 1149   startDrag: function(x, y) {
 1150 
 1151     this.createIframeMasks();
 1152 
 1153     this.lastMoveTarget = null;
 1154 
 1155     // create nice dragging ghost
 1156     this.createGhost();
 1157 
 1158     // get srcEl (the original) and dragEl (the ghost)
 1159     var srcEl = Ext.get(this.getEl());
 1160     var dragEl = Ext.get(this.getDragEl());
 1161 
 1162     // refresh constraints
 1163     this.panel.constrainToDesktop();
 1164     var dragHeight, rightC, bottomC;
 1165     if(this.panel.dock.undockable) {
 1166       if(this.panel.collapsed) {
 1167         dragHeight = this.panel.titleEl.getHeight();
 1168       }
 1169       else {
 1170         dragHeight = dragEl.getHeight();
 1171         dragHeight = dragHeight <= this.panel.titleEl.getHeight() ? srcEl.getHeight() : dragHeight;
 1172       }
 1173 
 1174       rightC = this.rightConstraint + srcEl.getWidth() - dragEl.getWidth();
 1175       bottomC = this.bottomConstraint + srcEl.getHeight() - dragHeight;
 1176       this.setXConstraint(this.leftConstraint, rightC);
 1177       this.setYConstraint(this.topConstraint, bottomC);
 1178     }
 1179     else {
 1180       if(this.panel.docked) {
 1181         this.setXConstraint(0, 0);
 1182       }
 1183     }
 1184 
 1185     // hide dragEl (will be shown by onDrag)
 1186     dragEl.hide();
 1187 
 1188     // raise panel's "window" above others
 1189     if(!this.panel.docked) {
 1190       this.panel.dock.raise(this.panel);
 1191     }
 1192 
 1193     // hide panel's shadow if any
 1194     this.panel.setShadow(false);
 1195 
 1196     // clear visibility of panel's body (was setup by animations)
 1197     this.panel.body.dom.style.visibility = '';
 1198 
 1199     // hide source panel if undocked
 1200     if(!this.panel.docked) {
 1201 //      srcEl.hide();
 1202       srcEl.setDisplayed(false);
 1203       dragEl.show();
 1204     }
 1205 
 1206   } // end of function startDrag
 1207   // }}}
 1208   // {{{
 1209   /**
 1210     * Called internally to create nice dragging proxy (ghost)
 1211     */
 1212   , createGhost: function() {
 1213 
 1214     // get variables
 1215     var srcEl = Ext.get(this.getEl());
 1216     var dragEl = Ext.get(this.getDragEl());
 1217     var panel = this.panel;
 1218     var dock = panel.dock;
 1219 
 1220     // adjust look of ghost
 1221     var am = Ext.ux.AccordionManager;
 1222     dragEl.addClass('x-dock-panel-ghost');
 1223     dragEl.applyStyles({border:'1px solid #84a0c4','z-index': am.zindex + am.zindexInc});
 1224 
 1225     // set size of ghost same as original
 1226     dragEl.setBox(srcEl.getBox());
 1227     if(panel.docked) {
 1228       if(panel.lastWidth && dock.undockable) {
 1229         dragEl.setWidth(panel.lastWidth);
 1230       }
 1231       if(!panel.collapsed && dock.undockable && (panel.lastHeight > panel.titleEl.getHeight())) {
 1232         dragEl.setHeight(panel.lastHeight);
 1233       }
 1234     }
 1235 
 1236     // remove unnecessary text nodes from srcEl
 1237     srcEl.clean();
 1238 
 1239     // setup title
 1240     var dragTitleEl = Ext.DomHelper.append(dragEl, {tag:'div'}, true);
 1241     dragTitleEl.update(srcEl.dom.firstChild.innerHTML);
 1242     dragTitleEl.dom.className = srcEl.dom.firstChild.className;
 1243     if(panel.collapsed && Ext.isIE) {
 1244       dragTitleEl.dom.style.borderBottom = "0";
 1245     }
 1246 
 1247   } // end of function createGhost
 1248   // }}}
 1249   // {{{
 1250   /**
 1251     * Default DDProxy onDragOver override
 1252     * It is called when dragging over a panel
 1253     * or over the dock.body DropZone
 1254     *
 1255     * @param {Event} e not used
 1256     * @param {String} targetId id of the target we're over
 1257     *
 1258     * Beware: While dragging over docked panels it's called
 1259     * twice. Once for panel and once for DropZone
 1260     */
 1261   , onDragOver: function(e, targetId) {
 1262 
 1263     // get panel element
 1264     var srcEl = Ext.get(this.getEl());
 1265 
 1266     // get target panel and dock
 1267     var targetDock = Ext.ux.AccordionManager.get(targetId);
 1268     var targetPanel = targetDock ? targetDock.items.get(targetId) : this.panel;
 1269 
 1270     // setup current target for endDrag
 1271     if(targetPanel) {
 1272       this.currentTarget = targetPanel.id;
 1273     }
 1274     if(targetDock && !this.currentTarget) {
 1275       this.currentTarget = targetDock.id;
 1276     }
 1277 
 1278     // landing indicators
 1279     if(targetPanel && targetPanel.docked && !this.panel.dock.forceOrder) {
 1280       targetPanel.titleEl.addClass('x-dock-panel-title-dragover');
 1281     }
 1282     if(targetDock) {
 1283       targetDock.body.addClass('x-dock-body-dragover');
 1284     }
 1285     if(this.panel.docked) {
 1286       this.panel.titleEl.addClass('x-dock-panel-title-dragover');
 1287     }
 1288 
 1289     // reorder panels in dock if we're docked too
 1290     var targetEl;
 1291 
 1292     if(targetDock === this.panel.dock
 1293         && targetPanel
 1294         && targetPanel.docked
 1295         && this.panel.docked
 1296         && !this.panel.dock.forceOrder) {
 1297       targetEl = targetPanel.el;
 1298 
 1299       if(targetPanel.collapsed || this.lastMoveTarget !== targetPanel) {
 1300         if(this.movingUp) {
 1301           srcEl.insertBefore(targetEl);
 1302           this.lastMoveTarget = targetPanel;
 1303         }
 1304         else {
 1305           srcEl.insertAfter(targetEl);
 1306           this.lastMoveTarget = targetPanel;
 1307         }
 1308       }
 1309       this.DDM.refreshCache(this.groups);
 1310     }
 1311 
 1312   } // end of function onDragOver
 1313   // }}}
 1314   // {{{
 1315   /**
 1316     * called internally to attach this.panel to accordion
 1317     * @param {Ext.ux.Accordion} targetDock the dock to attach the panel to
 1318     */
 1319   , attachToDock: function(targetDock) {
 1320     if(targetDock && this.panel.dock !== targetDock) {
 1321       // detach panel
 1322       this.panel.dock.detach(this.panel);
 1323 
 1324       // attach panel
 1325       targetDock.attach(this.panel);
 1326 
 1327     }
 1328   }
 1329   // }}}
 1330   // {{{
 1331   /**
 1332     * Called internally when cursor leaves a drop target
 1333     * @param {Ext.Event} e
 1334     * @param {String} targetId id of target we're leaving
 1335     */
 1336   , onDragOut: function(e, targetId) {
 1337 
 1338     var targetDock = Ext.ux.AccordionManager.get(targetId);
 1339     var targetPanel = targetDock ? targetDock.items.get(targetId) : this.panel;
 1340 
 1341     if(targetDock) {
 1342       targetDock.body.removeClass('x-dock-body-dragover');
 1343     }
 1344 
 1345     if(targetPanel) {
 1346       targetPanel.titleEl.removeClass('x-dock-panel-title-dragover');
 1347     }
 1348     this.currentTarget = null;
 1349   }
 1350   // }}}
 1351   // {{{
 1352   /**
 1353     * Default DDProxy onDrag override
 1354     *
 1355     * It's called while dragging
 1356     * @param {Event} e used to get coordinates
 1357     */
 1358   , onDrag: function(e) {
 1359 
 1360     // get source (original) and proxy (ghost) elements
 1361     var srcEl = Ext.get(this.getEl());
 1362     var dragEl = Ext.get(this.getDragEl());
 1363 
 1364     if(!dragEl.isVisible()) {
 1365       dragEl.show();
 1366     }
 1367 
 1368     var y = e.getPageY();
 1369 
 1370     this.movingUp = this.lastY > y;
 1371     this.lastY = y;
 1372 
 1373   } // end of function onDrag
 1374   // }}}
 1375   // {{{
 1376   /**
 1377     * Default DDProxy endDrag override
 1378     *
 1379     * Called when dragging is finished
 1380     */
 1381   , endDrag: function() {
 1382 
 1383     this.destroyIframeMasks();
 1384 
 1385     // get the source (original) and proxy (ghost) elements
 1386     var srcEl = Ext.get(this.getEl());
 1387     var dragEl = Ext.get(this.getDragEl());
 1388 
 1389     srcEl.setDisplayed(true);
 1390 
 1391     // get box and hide the ghost
 1392     var box = dragEl.getBox();
 1393 
 1394     var sourceDock = this.panel.dock;
 1395     var targetDock = Ext.ux.AccordionManager.get(this.currentTarget);
 1396     var targetPanel = targetDock ? targetDock.items.get(this.currentTarget) : this.panel;
 1397     var orderChanged = false;
 1398 
 1399     // remove any dragover classes from panel title and dock
 1400     this.panel.titleEl.removeClass('x-dock-panel-title-dragover');
 1401     this.dock.body.removeClass('x-dock-body-dragover');
 1402     if(targetDock) {
 1403       targetDock.items.each(function(panel) {
 1404         panel.titleEl.removeClass('x-dock-panel-title-dragover');
 1405       });
 1406     }
 1407 
 1408     // undock (docked panel dropped out of dock)
 1409     if(!this.panel.dock.catchPanels && (this.panel.docked && !this.currentTarget && !targetDock) || (targetPanel && !targetPanel.docked)) {
 1410       this.dock.undock(this.panel, box);
 1411       orderChanged = true;
 1412     }
 1413 
 1414     // dock undocked panel
 1415     else if(!this.panel.docked) {
 1416       this.attachToDock(targetDock);
 1417       this.panel.dock.dock(this.panel, this.currentTarget);
 1418       orderChanged = true;
 1419     }
 1420 
 1421     // do nothing for panel moved over it's own dock
 1422     // handling has already been done by onDragOver
 1423     else if(this.panel.docked && (this.panel.dock === targetDock)) {
 1424       // do nothing on purpose - do not remove
 1425       orderChanged = true;
 1426     }
 1427 
 1428     // dock panel to another dock
 1429     else if(this.currentTarget || targetDock) {
 1430       this.attachToDock(targetDock);
 1431       if(targetDock) {
 1432         targetDock.body.removeClass('x-dock-body-dragover');
 1433         this.panel.docked = false;
 1434         targetDock.dock(this.panel, this.currentTarget);
 1435       }
 1436       orderChanged = true;
 1437     }
 1438 
 1439     // just free dragging
 1440     if(!this.panel.docked) {
 1441       this.panel.setBox(box);
 1442 
 1443       // let the state manager know the new panel position
 1444       this.dock.fireEvent('panelbox', this.panel, {x:box.x, y:box.y, width:box.width, height:box.height});
 1445     }
 1446 
 1447     // clear the ghost content, hide id and move it off screen
 1448     dragEl.hide();
 1449     dragEl.update('');
 1450     dragEl.applyStyles({
 1451       top:'-9999px'
 1452       , left:'-9999px'
 1453       , height:'0px'
 1454       , width:'0px'
 1455     });
 1456 
 1457     if(orderChanged) {
 1458       sourceDock.updateOrder();
 1459       if(targetDock && targetDock !== sourceDock) {
 1460         targetDock.updateOrder();
 1461       }
 1462     }
 1463     this.DDM.refreshCache(this.groups);
 1464 
 1465   } // end of function endDrag
 1466   // }}}
 1467   // {{{
 1468   , createIframeMasks: function() {
 1469     this.destroyIframeMasks();
 1470 
 1471     var masks = [];
 1472     var iframes = Ext.get(document.body).select('iframe');
 1473     iframes.each(function(iframe) {
 1474       var mask = Ext.DomHelper.append(document.body, {tag:'div'}, true);
 1475       mask.setBox(iframe.getBox());
 1476       masks.push(mask);
 1477     });
 1478     this.iframeMasks = masks;
 1479   }
 1480   // }}}
 1481   // {{{
 1482   , destroyIframeMasks: function() {
 1483     if(!this.iframeMasks || ! this.iframeMasks.length) {
 1484       return;
 1485     }
 1486     for(var i = 0; i < this.iframeMasks.length; i++) {
 1487       this.iframeMasks[i].remove();
 1488     }
 1489     this.iframeMasks = [];
 1490   }
 1491   // }}}
 1492 
 1493 });
 1494 // }}}
 1495 // {{{
 1496 /**
 1497   * Private class for keeping and restoring state of the Accordion
 1498   */
 1499 Ext.ux.AccordionStateManager = function() {
 1500   this.state = { docks:{}, panels:{} };
 1501 };
 1502 
 1503 Ext.ux.AccordionStateManager.prototype = {
 1504   init: function(provider) {
 1505 
 1506     // save state provider
 1507     this.provider = provider;
 1508 //    var state = provider.get('accjs-state');
 1509 //    if(state) {
 1510 //      this.state = state;
 1511 //    }
 1512     state = this.state;
 1513 
 1514     var am = Ext.ux.AccordionManager;
 1515     var dockState;
 1516 
 1517     // {{{
 1518     // docks loop
 1519     am.each(function(dock) {
 1520       if(false === dock.keepState) {
 1521         return;
 1522       }
 1523 
 1524       state.docks[dock.id] = provider.get('accjsd-' + dock.id);
 1525       dockState = state.docks[dock.id];
 1526       if(dockState) {
 1527 
 1528         // {{{
 1529         // handle docks (accordions)
 1530         if(dockState) {
 1531 
 1532           // {{{
 1533           // restore order of panels
 1534           if(dockState.order) {
 1535             dock.setOrder(dockState.order);
 1536           }
 1537           // }}}
 1538           // {{{
 1539           // restore independent
 1540           if(undefined !== dockState.independent) {
 1541             dock.setIndependent(dockState.independent);
 1542           }
 1543           // }}}
 1544           // {{{
 1545           // restore undockable
 1546           if(undefined !== dockState.undockable) {
 1547             dock.setUndockable(dockState.undockable);
 1548           }
 1549           // }}}
 1550           // {{{
 1551           // restore useShadow
 1552           if(undefined !== dockState.useShadow) {
 1553             dock.setShadow(dockState.useShadow);
 1554           }
 1555           // }}}
 1556 
 1557         } // end of if(dockState)
 1558         // }}}
 1559 
 1560       }
 1561 
 1562       // install event handlers on docks
 1563       dock.on({
 1564         orderchange: {scope:this, fn:this.onOrderChange}
 1565         , independent: {scope:this, fn:this.onIndependent}
 1566         , undockable: {scope:this, fn:this.onUndockable}
 1567         , useshadow: {scope: this, fn: this.onUseShadow}
 1568         , panelexpand: {scope: this, fn: this.onPanelCollapse}
 1569         , panelcollapse: {scope: this, fn: this.onPanelCollapse}
 1570         , panelpinned: {scope: this, fn: this.onPanelPinned}
 1571         , paneldock: {scope: this, fn: this.onPanelUnDock}
 1572         , panelundock: {scope: this, fn: this.onPanelUnDock}
 1573         , boxchange: {scope: this, fn: this.onPanelUnDock}
 1574         , panelbox: {scope: this, fn: this.onPanelUnDock}
 1575       });
 1576     }, this);
 1577     // }}}
 1578 
 1579     // {{{
 1580     // panels loop
 1581     am.each(function(dock) {
 1582       if(!dock.keepState) {
 1583         return;
 1584       }
 1585 
 1586       // panels within dock loop
 1587       var panelState;
 1588       dock.items.each(function(panel) {
 1589 
 1590         state.panels[panel.id] = provider.get('accjsp-' + panel.id);
 1591         panelState = state.panels[panel.id];
 1592 
 1593         if(panelState) {
 1594 
 1595           // {{{
 1596           // restore docked/undocked state
 1597           if(undefined !== panelState.docked) {
 1598             if(!panelState.docked) {
 1599               if('object' === typeof panelState.box) {
 1600                 panel.docked = true;
 1601                 panel.dock.undock(panel, panelState.box);
 1602               }
 1603             }
 1604           }
 1605           // }}}
 1606           // {{{
 1607           // restore pinned state
 1608           if(undefined !== panelState.pinned) {
 1609             panel.pinned = panelState.pinned;
 1610             if(panel.pinned) {
 1611               panel.expand(true);
 1612             }
 1613             else {
 1614               panel.updateVisuals();
 1615             }
 1616           }
 1617           // }}}
 1618           // {{{
 1619           // restore collapsed/expanded state
 1620           if(undefined !== panelState.collapsed) {
 1621             if(panelState.collapsed) {
 1622               panel.collapsed = false;
 1623               panel.collapse(true);
 1624             }
 1625             else {
 1626               panel.collapsed = true;
 1627               panel.expand(true);
 1628             }
 1629           }
 1630           // }}}
 1631 
 1632         }
 1633       }, this); // end of panels within dock loop
 1634     }, this); // end of docks loop
 1635     // }}}
 1636 
 1637 
 1638   }
 1639 
 1640   // event handlers
 1641   // {{{
 1642   , onOrderChange: function(dock, order) {
 1643     if(false !== dock.keepState) {
 1644       this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
 1645       this.state.docks[dock.id].order = order;
 1646       this.storeDockState(dock);
 1647     }
 1648   }
 1649   // }}}
 1650   // {{{
 1651   , onIndependent: function(dock, independent) {
 1652     if(false !== dock.keepState) {
 1653       this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
 1654       this.state.docks[dock.id].independent = independent;
 1655       this.storeDockState(dock);
 1656     }
 1657   }
 1658   // }}}
 1659   // {{{
 1660   , onUndockable: function(dock, undockable) {
 1661     if(false !== dock.keepState) {
 1662       this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
 1663       this.state.docks[dock.id].undockable = undockable;
 1664       this.storeDockState(dock);
 1665     }
 1666   }
 1667   // }}}
 1668   // {{{
 1669   , onUseShadow: function(dock, shadow) {
 1670     if(false !== dock.keepState) {
 1671       this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
 1672       this.state.docks[dock.id].useShadow = shadow;
 1673       this.storeDockState(dock);
 1674     }
 1675   }
 1676   // }}}
 1677   // {{{
 1678   , onPanelCollapse: function(panel) {
 1679     if(panel.dock.keepState) {
 1680       this.state.panels[panel.id] = this.state.panels[panel.id] || {};
 1681       this.state.panels[panel.id].collapsed = panel.collapsed;
 1682     }
 1683     else {
 1684       try {delete(this.state.panels[panel.id].collapsed);}
 1685       catch(e){}
 1686     }
 1687     this.storePanelState(panel);
 1688   }
 1689   // }}}
 1690   // {{{
 1691   , onPanelPinned: function(panel, pinned) {
 1692     if(panel.dock.keepState) {
 1693       this.state.panels[panel.id] = this.state.panels[panel.id] || {};
 1694       this.state.panels[panel.id].pinned = pinned;
 1695     }
 1696     else {
 1697       try {delete(this.state.panels[panel.id].pinned);}
 1698       catch(e){}
 1699     }
 1700     this.storePanelState(panel);
 1701   }
 1702   // }}}
 1703   // {{{
 1704   , onPanelUnDock: function(panel, box) {
 1705     if(panel.dock.keepState) {
 1706       this.state.panels[panel.id] = this.state.panels[panel.id] || {};
 1707       this.state.panels[panel.id].docked = panel.docked ? true : false;
 1708       this.state.panels[panel.id].box = box || null;
 1709     }
 1710     else {
 1711       try {delete(this.state.panels[panel.id].docked);}
 1712       catch(e){}
 1713       try {delete(this.state.panels[panel.id].box);}
 1714       catch(e){}
 1715     }
 1716 //    console.log('onPanelUnDock: ', + panel.id);
 1717     this.storePanelState(panel);
 1718   }
 1719   // }}}
 1720   // {{{
 1721   , storeDockState: function(dock) {
 1722     this.provider.set.defer(700, this, ['accjsd-' + dock.id, this.state.docks[dock.id]]);
 1723   }
 1724   // }}}
 1725   // {{{
 1726   , storePanelState: function(panel) {
 1727     this.provider.set.defer(700, this, ['accjsp-' + panel.id, this.state.panels[panel.id]]);
 1728   }
 1729   // }}}
 1730 
 1731 }; // end of Ext.ux.AccordionManager.prototype
 1732 // }}}
 1733 // {{{
 1734 /**
 1735   * Singleton to manage multiple accordions
 1736   * @singleton
 1737   */
 1738 Ext.ux.AccordionManager = function() {
 1739 
 1740   // collection of accordions
 1741   var items = new Ext.util.MixedCollection();
 1742 
 1743   // public stuff
 1744   return {
 1745     // starting z-index for panels
 1746     zindex: 9999
 1747     // z-index increment (2 as 1 is for shadow)
 1748     , zindexInc: 2
 1749 
 1750     // {{{
 1751     /**
 1752       * increments (by this.zindexInc) this.zindex and returns new value
 1753       * @return {Integer} next zindex value
 1754       */
 1755     , getNextZindex: function() {
 1756       this.zindex += this.zindexInc;
 1757       return this.zindex;
 1758     }
 1759     // }}}
 1760     // {{{
 1761     /**
 1762       * raises panel above others (in the same desktop)
 1763       * Maintains z-index stack
 1764       * @param {Ext.ux.InfoPanel} panel panel to raise
 1765       * @return {Ext.ux.InfoPanel} panel panel that has been raised
 1766       */
 1767     , raise: function(panel) {
 1768       items.each(function(dock) {
 1769         dock.items.each(function(p) {
 1770           if(p.zindex > panel.zindex) {
 1771             p.zindex -= this.zindexInc;
 1772             p.el.applyStyles({'z-index':p.zindex});
 1773             if(!p.docked) {
 1774               p.setShadow(true);
 1775             }
 1776           }
 1777         }, this);
 1778       }, this);
 1779 
 1780       if(panel.zindex !== this.zindex) {
 1781         panel.zindex = this.zindex;
 1782         panel.el.applyStyles({'z-index':panel.zindex});
 1783         if(panel.desktop.lastChild !== panel.el.dom) {
 1784           panel.dock.desktop.appendChild(panel.el.dom);
 1785         }
 1786         if(!panel.docked) {
 1787           panel.setShadow(true);
 1788         }
 1789       }
 1790 
 1791       return panel;
 1792     }
 1793     // }}}
 1794     // {{{
 1795     /**
 1796       * Adds accordion to items
 1797       * @param {Ext.ux.Accordion} acc accordion to add
 1798       * @return {Ext.ux.Accordion} added accordion
 1799       */
 1800     , add: function(acc) {
 1801       items.add(acc.id, acc);
 1802       return acc;
 1803     }
 1804     // }}}
 1805     // {{{
 1806     /**
 1807       * get accordion by it's id or by id of some ot it's panels
 1808       * @param {String} key id of accordion or panel
 1809       * @return {Ext.ux.Accordion} or undefined if not found
 1810       */
 1811     , get: function(key) {
 1812       var dock = items.get(key);
 1813       if(!dock) {
 1814         items.each(function(acc) {
 1815           if(dock) {
 1816             return;
 1817           }
 1818           var panel = acc.items.get(key);
 1819           if(panel) {
 1820             dock = panel.dock;
 1821           }
 1822         });
 1823       }
 1824       return dock;
 1825     }
 1826     // }}}
 1827   // {{{
 1828     /**
 1829       * get panel by it's id
 1830       * @param {String} key id of the panel to get
 1831       * @return {Ext.ux.InfoPanel} panel found or null
 1832       */
 1833     , getPanel: function(key) {
 1834       var dock = this.get(key);
 1835       return dock && dock.items ? this.get(key).items.get(key) : null;
 1836     }
 1837   // }}}
 1838     // {{{
 1839     /**
 1840       * Restores state of dock and panels
 1841       * @param {Ext.state.Provider} provider (optional) An alternate state provider
 1842       */
 1843     , restoreState: function(provider) {
 1844       if(!provider) {
 1845         provider = Ext.state.Manager;
 1846       }
 1847       var sm = new Ext.ux.AccordionStateManager();
 1848       sm.init(provider);
 1849 
 1850     }
 1851     // }}}
 1852 
 1853     , each: function(fn, scope) {
 1854       items.each(fn, scope);
 1855     }
 1856 
 1857   }; // end of return
 1858 
 1859 }();
 1860 // }}}
 1861 
 1862 // end of file