

// vim: ts=4:sw=4:nu:fdc=4:nospell

// Create user extensions namespace (Ext.ux)
Ext.namespace('Ext.ux');

/**
  * Ext.ux.InfoPanel Extension Class
  *
  * @author  Ing. Jozef Sakalos
  * @version $Id: Ext.ux.InfoPanel.js 153 2007-08-24 10:46:19Z jozo $
  *
  * @class Ext.ux.InfoPanel
  * @extends Ext.ContentPanel
  * @constructor
  * Creates new Ext.ux.InfoPanel
  * @param {String/HTMLElement/Element} el The container element for this panel
  * @param {String/Object} config A string to set only the title or a config object
  * @param {String} content (optional) Set the HTML content for this panel
  * @cfg {Boolean} animate set to true to switch animation of expand/collapse on (defaults to undefined)
  * @cfg {String} bodyClass css class added to the body in addition to the default class(es)
  * @cfg {String/HTMLElement/Element} bodyEl This element is used as body of panel.
  * @cfg {String} buttonPosition set this to 'left' to place expand button to the left of titlebar
  * @cfg {Boolean} collapsed false to start with the expanded body (defaults to true)
  * @cfg {String} collapsedIcon Path for icon to display in the title when panel is collapsed
  * @cfg {Boolean} collapseOnUnpin unpinned panel is collapsed when possible (defaults to true)
  * @cfg {Boolean} collapsible false to disable collapsibility (defaults to true)
  * @cfg {Boolean} draggable true to allow panel dragging (defaults to undefined)
  * @cfg {Float} duration Duration of animation in seconds (defaults to 0.35)
  * @cfg {String} easingCollapse Easing to use for collapse animation (e.g. 'backIn')
  * @cfg {String} easingExpand Easing to use for expand animation (e.g. 'backOut')
  * @cfg {String} expandedIcon Path for icon to display in the title when panel is expanded
  * @cfg {String} icon Path for icon to display in the title
  * @cfg {Integer} minWidth minimal width in pixels of the resizable panel (defaults to 0)
  * @cfg {Integer} maxWidth maximal width in pixels of the resizable panel (defaults to 9999)
  * @cfg {Integer} minHeight minimal height in pixels of the resizable panel (defaults to 50)
  * @cfg {Integer} maxHeight maximal height in pixels of the resizable panel (defaults to 9999)
  * @cfg {String} panelClass Set to override the default 'x-dock-panel' class.
  * @cfg {Boolean} pinned true to start in pinned state (implies collapsed:false) (defaults to false)
  * @cfg {Boolean} resizable true to allow use resize width of the panel. (defaults to undefined)
  *  Handles are transparent. (defaults to false)
  * @cfg {String} shadowMode defaults to 'sides'.
  * @cfg {Boolean} showPin Show the pin button - makes sense only if panel is part of Accordion
  * @cfg {String} trigger 'title' or 'button'. Click where expands/collapses the panel (defaults to 'title')
  * @cfg {Boolean} useShadow Use shadows for undocked panels or panels w/o dock. (defaults to undefined = don't use)
  */
Ext.ux.InfoPanel = function(el, config, content) {

	config = config || el;
	// {{{
	// basic setup
	var oldHtml = content || null;
	if(config && config.content) {
		oldHtml = oldHtml || config.content;
		delete(config.content);
	}

	// save autoScroll to this.bodyScroll
	if(config && config.autoScroll) {
		this.bodyScroll = config.autoScroll;
		delete(config.autoScroll);
	}

	var url;
	if(el && el.url) {
		url = el.url;
		delete(el.url);
	}
	if(config && config.url) {
		url = config.url;
		delete(config.url);
	}

	// call parent constructor
	Ext.ux.InfoPanel.superclass.constructor.call(this, el, config);

	this.desktop = Ext.get(this.desktop) || Ext.get(document.body);

	// shortcut of DomHelper
	var dh = Ext.DomHelper, oldTitleEl;

	this.el.clean();
	this.el.addClass(this.panelClass);

	// handle autoCreate
	if(this.autoCreate) {
		oldHtml = this.el.dom.innerHTML;
		this.el.update('');
		this.desktop.appendChild(this.el);
		this.el.removeClass('x-layout-inactive-content');
	}
	// handle markup
	else {
		this.el.clean();
		if(this.el.dom.firstChild && !this.bodyEl) {
			this.title = this.title || this.el.dom.firstChild.innerHTML;
			if(this.el.dom.firstChild.nextSibling) {
				this.body = Ext.get(this.el.dom.firstChild.nextSibling);
			}
			oldTitleEl = this.el.dom.firstChild;
			oldTitleEl = oldTitleEl.parentNode.removeChild(oldTitleEl);
			oldTitleEl = null;
		}
	}

	// get body element
	if(this.bodyEl) {
		this.body = Ext.get(this.bodyEl);
		this.el.appendChild(this.body);
	}
	// }}}
	// {{{
	// create title element
	var create;
	if('left' === this.buttonPosition ) {
		create = {
			tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
				{tag:'table', cellspacing:0, children: [
					{tag:'tr', children: [
						{tag:'td', children:[
							{tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
						]}
						, {tag:'td', width:'100%', children: [
							{tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
						]}
						, {tag:'td', cls:'x-dock-panel-title-icon-ct', children: [
							{tag:'img', alt:'', cls:'x-dock-panel-title-icon'}
						]}
					]}
				]}
			]};
	}
	else {
		create = {
			tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
				{tag:'table', cellspacing:0, children: [
					{tag:'tr', children: [
						{tag:'td', cls:'x-dock-panel-title-icon-ct', children: [
							{tag:'img', alt:'', cls:'x-dock-panel-title-icon'}
						]}
						, {tag:'td', width:'100%', children: [
							{tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
						]}
						, {tag:'td', children:[
							{tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
						]}
					]}
				]}
			]};
	}
	this.titleEl = dh.insertFirst(this.el.dom, create, true);
	this.iconImg = this.titleEl.select('img.x-dock-panel-title-icon').item(0);
	this.titleEl.addClassOnOver('x-dock-panel-title-over');
	this.titleEl.enableDisplayMode();
	this.titleTextEl = Ext.get(this.titleEl.select('.x-dock-panel-title-text').elements[0]);
	this.tools = Ext.get(this.titleEl.select('.x-dock-panel-tools').elements[0]);
	if('right' === this.titleTextAlign) {
		this.titleTextEl.addClass('x-dock-panel-title-right');
	}

	this.tm = Ext.util.TextMetrics.createInstance(this.titleTextEl);
	// }}}
	// {{{
	// set title
	if(this.title) {
		this.setTitle(this.title);
	}
	// }}}
	// {{{
	// create pin button
	if(this.showPin) {
		this.stickBtn = this.createTool(this.tools.dom, 'x-layout-stick');
		this.stickBtn.enableDisplayMode();
		this.stickBtn.on('click', function(e, target) {
			e.stopEvent();
			this.pinned = ! this.pinned;
			this.updateVisuals();
			this.fireEvent('pinned', this, this.pinned);
		}, this);
		this.stickBtn.hide();	
	}
	// }}}
	// {{{
	// create collapse button
	if(this.collapsible) {
		this.collapseBtn = this.createTool(this.tools.dom
			, (this.collapsed ? 'x-layout-collapse-east' : 'x-layout-collapse-south')
		);
		this.collapseBtn.enableDisplayMode();
		if('title' === this.trigger) {
			this.titleEl.addClass('x-window-header-text');
			this.titleEl.on({
				  click:{scope: this, fn:this.toggle}
				, selectstart:{scope: this, fn: function(e) {
						e.preventDefault();
						return false;
				}}
			}, this);
		}
		else {
			this.collapseBtn.on("click", this.toggle, this);
		}
	}
	// }}}
	// {{{
	// create body if it doesn't exist yet
	if(!this.body) {
			this.body = dh.append(this.el, {
				tag: 'div'
				, cls: this.bodyClass || null
				, html: oldHtml || ''
				}, true);
	}
	this.body.enableDisplayMode();
	if(this.collapsed && !this.pinned) {
		this.body.hide();
	}
	else if(this.pinned) {
		this.body.show();
		this.collapsed = false;
	}
	this.body.addClass(this.bodyClass);
	this.body.addClass('x-dock-panel-body-undocked');

	// bodyScroll

	this.scrollEl = this.body;

	// autoScroll -> bodyScroll is experimental due to IE bugs
	this.scrollEl.setStyle('overflow', 
		this.bodyScroll === true && !this.collapsed ? 'auto' : 'hidden');
	// }}}

	if(this.fixedHeight) {
		this.setHeight(this.fixedHeight);
	}

	if(url) {
		this.setUrl(url, this.params, this.loadOnce);
	}

	// install hook for title context menu
	if(this.titleMenu) {
		this.setTitleMenu(this.titleMenu);
	}

	// install hook for icon menu
	if(this.iconMenu) {
		this.setIconMenu(this.iconMenu);
	}

	// {{{
	// add events
	this.addEvents({
		/**
			* @event beforecollapse
			* Fires before collapse is taking place. Return false to cancel collapse
			* @param {Ext.ux.InfoPanel} this
			*/
		beforecollapse: true
		/**
			* @event collapse
			* Fires after collapse
			* @param {Ext.ux.InfoPanel} this
			*/
		, collapse: true
		/**
			* @event beforecollapse
			* Fires before expand is taking place. Return false to cancel expand
			* @param {Ext.ux.InfoPanel} this
			*/
		, beforeexpand: true
		/**
			* @event expand
			* Fires after expand
			* @param {Ext.ux.InfoPanel} this
			*/
		, expand: true
		/**
			* @event pinned
			* Fires when panel is pinned/unpinned
			* @param {Ext.ux.InfoPanel} this
			* @param {Boolean} pinned true if the panel is pinned
			*/
		, pinned: true
		/**
			* @event animationcompleted
			* Fires when animation is completed
			* @param {Ext.ux.InfoPanel} this
			*/
		, animationcompleted: true
		/**
			* @event boxchange
			* Fires when the panel is resized
			* @param {Ext.ux.InfoPanel} this
			* @param {Object} box
			*/
		, boxchange: true

		/**
			* @event redize
			* Fires when info panel is resized
			* @param {Ext.ux.InfoPanel} this
			* @param {Integer} width New width
			* @param {Integer} height New height
			*/
		, resize: true

		/**
			* @event destroy
			* Fires after the panel is destroyed
			* @param {Ext.ux.InfoPanel} this
			*/
		, destroy: true

	});
	// }}}
	// {{{
	// setup dragging, resizing, and shadow
	this.setDraggable(this.draggable);
	this.setResizable(!this.collapsed);
	this.setShadow(this.useShadow);

	// }}}

	this.el.setStyle('z-index', this.zindex);
	this.updateVisuals();

	this.id = this.id || this.el.id;

}; // end of constructor

// extend
Ext.extend(Ext.ux.InfoPanel, Ext.ContentPanel, {

	// {{{
	// defaults
	  adjustments: [0,0]
	, collapsible: true
	, collapsed: true
	, collapseOnUnpin: true
	, pinned: false
	, trigger: 'title'
	, animate: undefined
	, duration: 0.35
	, draggable: undefined
	, resizable: undefined
	, docked: false
	, useShadow: undefined
	, bodyClass: 'x-dock-panel-body'
	, panelClass: 'x-dock-panel'
	, shadowMode: 'sides'
	, dragPadding: {
		  left:8
		, right:16
		, top:0
		, bottom:8
	}
	, lastWidth: 0
	, lastHeight: 0
	, minWidth: 0
	, maxWidth: 9999
	, minHeight: 50
	, maxHeight: 9999
	, autoScroll: false
	, fixedHeight: undefined
	, zindex: 10000
	// }}}
	// {{{
	/**
		* Called internally to create collapse button
		* Calls utility method of Ext.LayoutRegion createTool
		* @param {Element/HTMLElement/String} parentEl element to create the tool in
		* @param {String} className class of the tool
		*/
	, createTool : function(parentEl, className){
		return Ext.LayoutRegion.prototype.createTool(parentEl, className);
  }
	// }}}
	// {{{
	/**
		* Set title of the InfoPanel
		* @param {String} title Title to set
		* @return {Ext.ux.InfoPanel} this
		*/
	, setTitle: function(title) {
		this.title = title;
		this.titleTextEl.update(title);
		this.setIcon();
		return this;
	}
	// }}}
	// {{{
	/**
		* Set the icon to display in title
		* @param {String} iconPath path to use for src property of icon img
		*/
	, setIcon: function(iconPath) {
		iconPath = iconPath || (this.collapsed ? this.collapsedIcon : this.expandedIcon) || this.icon;
		if(iconPath) {
			this.iconImg.dom.src = iconPath;
		}
		else {
			this.iconImg.dom.src = Ext.BLANK_IMAGE_URL;
		}
	}
	// }}}
	// {{{
	/**
		* Assigns menu to title icon
		* @param {Ext.menu.Menu} menu menu to assign
		*/
	, setIconMenu: function(menu) {
		if(this.iconMenu) {
			this.iconImg.removeAllListeners();
		}
		menu.panel = this;
		this.iconImg.on({
			click: {
				scope: this
				, fn: function(e, target) {
				e.stopEvent();
				menu.showAt(e.xy);
			}}
		});
		this.iconMenu = menu;
	}
	// }}}
	// {{{
	/**
		* private - title menu click handler
		* @param {Ext.Event} e event
		* @param {Element} target target
		*/
	, onTitleMenu: function(e, target) {
		e.stopEvent();
		e.preventDefault();
		this.titleMenu.showAt(e.xy);	
	}
	// }}}
	// {{{
	/**
		* Assigns context menu (right click) to the title 
		* @param {Ext.menu.Menu} menu menu to assign
		*/
	, setTitleMenu: function(menu) {
		if(this.titleMenu) {
			this.titleEl.un('contextmenu', this.onTitleMenu, this);
		}
		menu.panel = this;
		this.titleEl.on('contextmenu', this.onTitleMenu, this);
		this.titleMenu = menu;
	}
	// }}}
	// {{{
	/**
		* Get current title
		* @return {String} Current title
		*/
	, getTitle: function() {
		return this.title;
	}
	// }}}
	// {{{
	/**
		* Returns body element
		* This overrides the ContentPanel getEl for convenient access to the body element
		* @return {Element} this.body
		*/
	, getEl: function() {
		return this.body;
	}
	// }}}
	// {{{
	/**
		* Returns title height
		* @return {Integer} title height
		*/
	, getTitleHeight: function() {
		return this.titleEl.getComputedHeight();
	}
	// }}}
	// {{{
	/**
		* Returns body height
		* @return {Integer} body height
		*/
	, getBodyHeight: function() {
		return this.body.getComputedHeight();
	}
	// }}}
	// {{{
	/**
		* Returns panel height
		* @return {Integer} panel height
		*/
	, getHeight: function() {
		return this.getBodyHeight() + this.getTitleHeight();
	}
	// }}}
	// {{{
	/**
		* Returns body client height
		* @return {Integer} body client height
		*/
	, getBodyClientHeight: function() {
		return this.body.getHeight(true);
	}
	// }}}
	// {{{
	/**
		* Update the innerHTML of this element, optionally searching for and processing scripts
    * @param {String} html The new HTML
    * @param {Boolean} loadScripts (optional) true to look for and process scripts
    * @param {Function} callback For async script loading you can be noticed when the update completes
    * @return {Ext.Element} this
		*/
	, update: function(html, loadScripts, callback) {
		this.body.update(html, loadScripts, callback);
		return this;
	}
	// }}}
	// {{{
	/**
	 * Updates this panel's element
	 * @param {String} content The new content
	 * @param {Boolean} loadScripts (optional) true to look for and process scripts
	*/
	, setContent: function(content, loadScripts) {
			this.body.update(content, loadScripts);
	}
	// }}}
	// {{{
	/**
	 * Get the {@link Ext.UpdateManager} for this panel. Enables you to perform Ajax updates.
	 * @return {Ext.UpdateManager} The UpdateManager
	 */
	, getUpdateManager: function() {
			return this.body.getUpdateManager();
	}
	// }}}
	// {{{
	/**
	 * The only required property is url. The optional properties nocache, text and scripts 
	 * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this panel UpdateManager instance.
	 * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1&amp;param2=2" or an object {param1: 1, param2: 2}
	 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess, oResponse)
	 * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used url. If true, it will not store the url.
	 * @return {Ext.ContentPanel} this
	 */
	, load: function() {
			var um = this.getUpdateManager();
			um.update.apply(um, arguments);
			return this;
	}
	// }}}
	// {{{
	/**
	 * Set a URL to be used to load the content for this panel. When this panel is activated, the content will be loaded from that URL.
	 * @param {String/Function} url The url to load the content from or a function to call to get the url
	 * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Ext.UpdateManager#update} for more details. (Defaults to null)
	 * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
	 * @return {Ext.UpdateManager} The UpdateManager
	 */
	, setUrl: function(url, params, loadOnce) {
			if(this.refreshDelegate){
					this.removeListener("expand", this.refreshDelegate);
			}
			this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
			this.on("expand", this.refreshDelegate);
			this.on({
				beforeexpand: {
					scope: this
					, single: this.loadOnce ? true : false
					, fn: function() {
						this.body.update('');
				}}
			});
			return this.getUpdateManager();
	}
	// }}}
	// {{{
	, _handleRefresh: function(url, params, loadOnce) {
			var updater;
			if(!loadOnce || !this.loaded){
					updater = this.getUpdateManager();
					updater.on({
						update: {
							scope: this
							, single: true
							, fn: function() {
								if(true === this.useShadow && this.shadow) {
									this.shadow.show(this.el);
								}
						}}
					});
					updater.update(url, params, this._setLoaded.createDelegate(this));
			}
	}
	// }}}
	// {{{
	, _setLoaded: function() {
			this.loaded = true;
	} 
	// }}}
  // {{{
	/**
	 *   Force a content refresh from the URL specified in the setUrl() method.
	 *   Will fail silently if the setUrl method has not been called.
	 *   This does not activate the panel, just updates its content.
	 */
	, refresh: function() {
			if(this.refreshDelegate){
				 this.loaded = false;
				 this.refreshDelegate();
			}
	}
	// }}}
	// {{{
	/**
		* Expands the panel
		* @param {Boolean} skipAnimation Set to true to skip animation
		* @return {Ext.ux.InfoPanel} this
		*/
	, expand: function(skipAnimation) {

		// do nothing if already expanded
		if(!this.collapsed) {
			return this;
		}

		// fire beforeexpand event
		if(false === this.fireEvent('beforeexpand', this)) {
			return this;
		}

		if(Ext.isGecko) {
			this.autoScrolls = this.body.select('{overflow=auto}');
			this.autoScrolls.setStyle('overflow', 'hidden');
		}

		// reset collapsed flag
		this.collapsed = false;

		this.autoSize();

		// hide shadow
		if(!this.docked) {
			this.setShadow(false);
		}

		// enable resizing
		if(this.resizer && !this.docked) {
			this.setResizable(true);
		}
		
		if(Ext.isIE) {
			this.body.setWidth(this.el.getWidth());
		}

		// animate expand
		if(true === this.animate && true !== skipAnimation) {
				this.body.slideIn('t', {
					easing: this.easingExpand || null
					, scope: this
					, duration: this.duration
					, callback: this.updateVisuals
				});
		}

		// don't animate, just show
		else {
			this.body.show();
			this.updateVisuals();
			this.fireEvent('animationcompleted', this);
		}

		// fire expand event
		this.fireEvent('expand', this);

		return this;

	}
	// }}}
	// {{{
	/**
		* Toggles the expanded/collapsed states
		* @param {Boolean} skipAnimation Set to true to skip animation
		* @return {Ext.ux.InfoPanel} this
		*/
	, toggle: function(skipAnimation) {
			if(this.collapsed) {
				this.expand(skipAnimation);
			}
			else {
				this.collapse(skipAnimation);
			}
			return this;
	}
	// }}}
	// {{{
	/**
		* Collapses the panel
		* @param {Boolean} skipAnimation Set to true to skip animation
		* @return {Ext.ux.InfoPanel} this
		*/
	, collapse: function(skipAnimation) {

		// do nothing if already collapsed or pinned
		if(this.collapsed || this.pinned) {
			return this;
		}

		// fire beforecollapse event
		if(false === this.fireEvent('beforecollapse', this)) {
				return this;
		}

		if(Ext.isGecko) {
			this.autoScrolls = this.body.select('{overflow=auto}');
			this.autoScrolls.setStyle('overflow', 'hidden');
		}

		if(this.bodyScroll /*&& !Ext.isIE*/) {
			this.scrollEl.setStyle('overflow','hidden');
		}

		// set collapsed flag
		this.collapsed = true;

		// hide shadow
		this.setShadow(false);

		// disable resizing of collapsed panel
		if(this.resizer) {
			this.setResizable(false);
		}

		// animate collapse
		if(true === this.animate && true !== skipAnimation) {
				this.body.slideOut('t', {
					easing: this.easingCollapse || null
					, scope: this
					, duration: this.duration
					, callback: this.updateVisuals
				});
		}

		// don't animate, just hide
		else {
			this.body.hide();
			this.updateVisuals();
			this.fireEvent('animationcompleted', this);
		}

		// fire collapse event
		this.fireEvent('collapse', this);

		return this;

	}
	// }}}
	// {{{
	/**
		* Called internally to update class of the collapse button 
		* as part of expand and collapse methods
		*
		* @return {Ext.ux.InfoPanel} this
		*/
	, updateVisuals: function() {

			// handle collapsed state
			if(this.collapsed) {
				if(this.showPin) {
					if(this.collapseBtn) {
						this.collapseBtn.show();
					}
					if(this.stickBtn) {
						this.stickBtn.hide();
					}
				}
				if(this.collapseBtn) {
					Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
						'x-layout-collapse-south', 'x-layout-collapse-east'
					);
				}
				this.body.replaceClass('x-dock-panel-body-expanded', 'x-dock-panel-body-collapsed');
				this.titleEl.replaceClass('x-dock-panel-title-expanded', 'x-dock-panel-title-collapsed');
			}
			
			// handle expanded state
			else {
				if(this.showPin) {
					if(this.pinned) {	
						if(this.stickBtn) {
							Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stick', 'x-layout-stuck');
						}
						this.titleEl.addClass('x-dock-panel-title-pinned');
					}
					else {
						if(this.stickBtn) {
							Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stuck', 'x-layout-stick');
						}
						this.titleEl.removeClass('x-dock-panel-title-pinned');
					}
					if(this.collapseBtn) {
						this.collapseBtn.hide();
					}
					if(this.stickBtn) {
						this.stickBtn.show();
					}
				}
				else {
					if(this.collapseBtn) {
						Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
							'x-layout-collapse-east', 'x-layout-collapse-south'
						);
					}
				}
				this.body.replaceClass('x-dock-panel-body-collapsed', 'x-dock-panel-body-expanded');
				this.titleEl.replaceClass('x-dock-panel-title-collapsed', 'x-dock-panel-title-expanded');
			}

			// show shadow if necessary
			if(!this.docked) {
				this.setShadow(true);
			}

			if(this.autoScrolls) {
				this.autoScrolls.setStyle('overflow', 'auto');
			}

			this.setIcon();

			if(this.bodyScroll && !this.docked && !this.collapsed /*&& !Ext.isIE*/) {
				this.scrollEl.setStyle('overflow', 'auto');
			}

			this.constrainToDesktop();

			// fire animationcompleted event
			this.fireEvent('animationcompleted', this);

			// clear visibility style of body's children
			var kids = this.body.select('div[className!=x-grid-viewport],input{visibility}');
			kids.setStyle.defer(1, kids, ['visibility','']);

			// restore body overflow
			if(this.bodyScroll && !this.collapsed /*&& !Ext.isIE*/) {
				this.setHeight(this.getHeight());
				this.scrollEl.setStyle('overflow','auto');
			}

			return this;
	}
	// }}}
	// {{{
	/**
		* Creates toolbar
		* @param {Array} config Configuration for Ext.Toolbar
		* @param {Boolean} bottom true to create bottom toolbar. (defaults to false = top toolbar)
		* @return {Ext.Toolbar} Ext.Toolbar object
		*/
	, createToolbar: function(config, bottom) {

		// we need clean body
		this.body.clean();

		// copy body to new container
		this.scrollEl = Ext.DomHelper.append(document.body, {tag:'div'}, true);
		var el;
		while(el = this.body.down('*')) {
			this.scrollEl.appendChild(el);
		}

		if(this.bodyScroll) {
			this.body.setStyle('overflow', '');
			if(!this.collapsed) {
				this.scrollEl.setStyle('overflow', 'auto');
			}
		}

		var create = {tag:'div'}, tbEl;
		config = config || null;
		if(bottom) {
			this.body.appendChild(this.scrollEl);
			tbEl = Ext.DomHelper.append(this.body, create, true);
			tbEl.addClass('x-dock-panel-toolbar-bottom');
		}
		else {
			tbEl = Ext.DomHelper.insertFirst(this.body, create, true);
			tbEl.addClass('x-dock-panel-toolbar');
			this.body.appendChild(this.scrollEl);
		}
		this.toolbar = new Ext.Toolbar(tbEl, config);
		this.setHeight(this.getHeight());
		return this.toolbar;
	}
	// }}}
	// {{{
	/**
		* Set the panel draggable
		* Uses lazy creation of dd object
		* @param {Boolean} enable pass false to disable dragging
		* @return {Ext.ux.InfoPanel} this
		*/
	, setDraggable: function(enable) {

		if(true !== this.draggable) {
			return this;
		}

		// lazy create proxy
		var dragTitleEl;
		if(!this.proxy) {
			this.proxy = this.el.createProxy('x-dlg-proxy');

			// setup title
			dragTitleEl = Ext.DomHelper.append(this.proxy, {tag:'div'}, true);
			dragTitleEl.update(this.el.dom.firstChild.innerHTML);
			dragTitleEl.dom.className = this.el.dom.firstChild.className;
			if(this.collapsed && Ext.isIE) {
				dragTitleEl.dom.style.borderBottom = "0";
			}

			this.proxy.hide();
			this.proxy.setOpacity(0.5);
			this.dd = new Ext.dd.DDProxy(this.el.dom, 'PanelDrag', {
				dragElId: this.proxy.id
				, scroll: false
			});
			this.dd.scroll = false;
			this.dd.afterDrag = function() {
				this.panel.moveToViewport();
				if(this.panel && this.panel.shadow && !this.panel.docked) {
					this.panel.shadow.show(this.panel.el);
				}
			};

			this.constrainToDesktop();
			Ext.EventManager.onWindowResize(this.moveToViewport, this);
		}

		this.dd.panel = this;
		this.dd.setHandleElId(this.titleEl.id);
		if(false === enable) {
			this.dd.lock();
		}
		else {
			this.dd.unlock();
		}

		return this;
	}
	// }}}
	// {{{
	/**
		* Set the panel resizable
		* Uses lazy creation of the resizer object
		* @param {Boolean} pass false to disable resizing
		* @return {Ext.ux.InfoPanel} this
		*/
	, setResizable: function(enable) {

		if(true !== this.resizable) {
			return this;
		}

		// {{{
		// lazy create resizer
		if(!this.resizer) {

			// {{{
			// create resizer
			this.resizer = new Ext.Resizable(this.el, {
				handles: 's w e sw se'
				, minWidth: this.minWidth || this.tm.getWidth(this.getTitle()) + 56 || 48
				, maxWidth: this.maxWidth
				, minHeight: this.minHeight
				, maxHeight: this.maxHeight
				, transparent: true
				, draggable: false
			});
			// }}}
			// {{{
			// install event handlers
			this.resizer.on({
				beforeresize: {
					scope:this
					, fn: function(resizer, e) {
						var viewport = this.getViewport();
						var box = this.getBox();

						var pos = resizer.activeHandle.position;

						// left constraint
						if(pos.match(/west/)) {
							resizer.minX = viewport.x + (this.dragPadding.left || 8);
						}

						// down constraint
						var maxH;
						if(pos.match(/south/)) {
							resizer.oldMaxHeight = resizer.maxHeight;
							maxH = viewport.y + viewport.height - box.y - (this.dragPadding.bottom || 8);
							resizer.maxHeight = maxH < resizer.maxHeight ? maxH : resizer.maxHeight;
						}

						// right constraint
						var maxW;
						if(pos.match(/east/)) {
							resizer.oldMaxWidth = resizer.maxWidth;
							maxW = viewport.x + viewport.width - box.x - (this.dragPadding.right || 10);
							resizer.maxWidth = maxW < resizer.maxWidth ? maxW : resizer.maxWidth;
						}
				}}
				, resize: {
					scope: this
					, fn: function(resizer, width, height, e) {
						resizer.maxHeight = resizer.oldMaxHeight || resizer.maxHeight;
						resizer.maxWidth = resizer.oldMaxWidth || resizer.maxWidth;
						this.setSize(width, height);
						this.constrainToDesktop();
						this.fireEvent('boxchange', this, this.el.getBox());
						this.fireEvent('resize', this, width, height);
						this.lastHeight = height;
						this.lastWidth = width;
				}}
			});
			// }}}

		}
		// }}}

		this.resizer.enabled = enable;

		// this is custom override of Ext.Resizer
		this.resizer.showHandles(enable);

		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally to clip passed width and height to viewport
		* @param {Integer} w width
		* @param {Integer} h height
		* @return {Object} {width:safeWidth, height:safeHeight}
		*/
	, safeSize: function(w, h) {
		var viewport = this.getViewport();
		var box = this.getBox();
		var gap = 0;
		var safeSize = {width:w, height:h};

		safeSize.height = 
			box.y + h + this.dragPadding.bottom + gap > viewport.height + viewport.y 
			? viewport.height - box.y + viewport.y - this.dragPadding.bottom - gap 
			: safeSize.height
		;

		safeSize.width = 
			box.x + w + this.dragPadding.right + gap > viewport.width + viewport.x
			? viewport.width - box.x + viewport.x - this.dragPadding.right - gap 
			: safeSize.width
		;

		return safeSize;
	}
	// }}}
	// {{{
	/**
		* Called internally to get current viewport
		* @param {Element/HTMLElement/String} desktop Element to get size and position of
		* @return {Object} viewport {x:x, y:y, width:width, height:height} x and y are page coords
		*/
	, getViewport: function(desktop) {

		desktop = desktop || this.desktop || document.body;
		var viewport = Ext.get(desktop).getViewSize();
		var xy;
		if(document.body === desktop.dom) {
			viewport.x = 0;
			viewport.y = 0;
		}
		else {
			xy = desktop.getXY();
			viewport.x = isNaN(xy[0]) ? 0 : xy[0];
			viewport.y = isNaN(xy[1]) ? 0 : xy[1];
		}

		return viewport;
	}
	// }}}
	// {{{
	/**
		* Sets the size of the panel. Demanded size is clipped to the viewport
		*
		* @param {Integer} w width to set
		* @param {Integer} h height to set
		* @return {Ext.ux.InfoPanel} this
		*/
	, setSize: function(w, h) {
		var safeSize = this.safeSize(w, h);
		this.setWidth(safeSize.width);
		this.setHeight(safeSize.height);
		if(Ext.isIE) {
			this.body.setWidth(safeSize.width);
		}

		if(!this.docked) {
			this.setShadow(true);
		}
	}
	// }}}
	// {{{
	/**
		* Sets the width of the panel. Demanded width is clipped to the viewport
		*
		* @param {Integer} w width to set
		* @return {Ext.ux.InfoPanel} this
		*/
	, setWidth: function(w) {
		this.el.setWidth(w);
		this.body.setStyle('width','');
		if(!this.docked) {
			this.setShadow(true);
		}
		this.lastWidth = w;

		return this;
	}
	// }}}
	// {{{
	/**
		* Sets the height of the panel. Demanded height is clipped to the viewport
		*
		* @param {Integer} h height to set
		* @return {Ext.ux.InfoPanel} this
		*/
	, setHeight: function(h) {
		var newH = h - this.getTitleHeight();
		var scrollH = newH;
		if(1 < newH) {
			if(this.scrollEl !== this.body) {
				scrollH -= this.toolbar ? this.toolbar.getEl().getHeight() : 0;
//				scrollH -= 27;
				scrollH -= this.adjustments[1] || 0;
				this.scrollEl.setHeight(scrollH);
			}
			this.body.setHeight(newH);
		}
		else {
			this.body.setStyle('height','');
		}

		if(!this.docked) {
			this.setShadow(true);
		}
//		this.lastHeight = h;
		this.el.setStyle('height','');

		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally to set x, y, width and height of the panel
		*
		* @param {Object} box
		* @return {Ext.ux.InfoPanel} this
		*/
	, setBox: function(box) {
		this.el.setBox(box);
		this.moveToViewport();
		this.setSize(box.width, box.height);

		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally to get the box of the panel
		*
		* @return {Object} box
		*/
	, getBox: function() {
		return this.el.getBox();
	}
	// }}}
	// {{{
	, autoSize: function() {

		var width = 0;
		var height = this.fixedHeight || 0;
		var dock = this.dock;

		// docked
		if(this.docked && this.dock) {
			if(dock.fitHeight) {
				height = dock.getPanelBodyHeight() + this.getTitleHeight();
			}
		}

		// undocked
		else {
			// height logic
			height = this.lastHeight || this.fixedHeight || 0;
			height = height < this.maxHeight ? height : (this.maxHeight < 9999 ? this.maxHeight : 0);
			height = (height && height < this.minHeight ) ? this.minHeight : height;
			this.lastHeight = height ? height : this.lastHeight;
		}

		this.setHeight(height);

	}
	// }}}
	// {{{
	/**
		* Turns shadow on/off
		* Uses lazy creation of the shadow object
		* @param {Boolean} shadow pass false to hide, true to show the shadow
		* @return {Ext.ux.InfoPanel} this
		*/
	, setShadow: function(shadow) {

		// if I have shadow but shouldn't use it
		if(this.shadow && true !== this.useShadow) {
			this.shadow.hide();
			return this;
		}

		// if I shouldn't use shadow
		if(true !== this.useShadow) {
			return this;
		}

		// if I don't have shadow
		if(!this.shadow) {
			this.shadow = new Ext.Shadow({mode:this.shadowMode});
		}

		// show or hide
		var zindex;
		if(shadow) {
			this.shadow.show(this.el);

			// fix the Ext shadow z-index bug
			zindex = parseInt(this.el.getStyle('z-index'), 10);
			zindex = isNaN(zindex) ? '' : zindex - 1;
			this.shadow.el.setStyle('z-index', zindex);
		}
		else {
			this.shadow.hide();
		}

		return this;

	}
	// }}}
	// {{{
	/**
		* Show the panel
		* @param {Boolean} show (optional) if false hides the panel instead of show
		* @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
		* @return {Ext.ux.InfoPanel} this
		*/
	, show: function(show, alsoUndocked) {

		// ignore undocked panels if not forced to
		if(!this.docked && true !== alsoUndocked) {
			return this;
		}

		show = (false === show ? false : true);
		if(!this.docked) {
			this.setShadow(show);
		}

		this.el.setStyle('display', show ? '' : 'none');
		return this;
	}
	// }}}
	// {{{
	/**
		* Hide the panel
		* @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
		* @return {Ext.ux.InfoPanel} this
		*/
	, hide: function(alsoUndocked) {
		this.show(false, alsoUndocked);
	}
	// }}}
	// {{{
	/**
		* Constrains dragging of this panel to desktop boundaries
		* @param {Element} desktop the panel is to be constrained to
		* @return {Ext.ux.InfoPanel} this
		*/
	, constrainToDesktop: function(desktop) {
		desktop = desktop || this.desktop;
		if(desktop && this.dd) {
			this.dd.constrainTo(desktop, this.dragPadding, false);
		}
		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally to move the panel to the viewport. 
		* Also constrains the dragging to the desktop
		*
		* @param {Object} viewport (optional) object {x:x, y:y, width:width, height:height}
		* @return {Ext.ux.InfoPanel} this
		*/
	, moveToViewport: function(viewport) {
		viewport = viewport && !isNaN(viewport.x) ? viewport : this.getViewport();
		var box = this.getBox();
		var moved = false;
		var gap = 10;

		// horizontal
		if(box.x + box.width + this.dragPadding.right > viewport.x + viewport.width) {
			moved = true;
			box.x = viewport.width + viewport.x - box.width - this.dragPadding.right - gap;
		}
		if(box.x - this.dragPadding.left < viewport.x) {
			moved = true;
			box.x = viewport.x + this.dragPadding.left + gap;
		}

		// vertical
		if(box.y + box.height + this.dragPadding.bottom > viewport.y + viewport.height) {
			moved = true;
			box.y = viewport.height + viewport.y - box.height - this.dragPadding.bottom - gap;
		}
		if(box.y - this.dragPadding.top < viewport.y) {
			moved = true;
			box.y = viewport.y + this.dragPadding.top + gap;
		}

		var oldOverflow;
		if(moved) {
			// sanity clip
			box.x = box.x < viewport.x ? viewport.x : box.x;
			box.y = box.y < viewport.y ? viewport.y : box.y;

			// prevent scrollbars from appearing
			this.desktop.oldOverflow = this.desktop.oldOverflow || this.desktop.getStyle('overflow');
			this.desktop.setStyle('overflow', 'hidden');

			// set position
			this.el.setXY([box.x, box.y]);

			// restore overflow
			this.desktop.setStyle.defer(100, this.desktop, ['overflow', this.desktop.oldOverflow]);

			if(!this.docked) {
				this.setShadow(true);
			}
		}

		this.constrainToDesktop();

		return this;
	}
	// }}}
	// {{{
	/**
		* destroys the panel
		*/
	, destroy: function() {
		if(this.shadow) {
			this.shadow.hide();
		}
		if(this.collapsible) {
			this.collapseBtn.removeAllListeners();
			this.titleEl.removeAllListeners();
		}

		if(this.resizer) {
			this.resizer.destroy();
		}
		if(this.dd) {
			if(this.proxy) {
				this.proxy.removeAllListeners();
				this.proxy.remove();
			}
			this.dd.unreg();
			this.dd = null;
		}
		if(this.dock) {
			this.dock.detach(this);
		}

		this.body.removeAllListeners();

		// call parent destroy
		Ext.ux.InfoPanel.superclass.destroy.call(this);

		this.fireEvent('destroy', this);

	}
	// }}}

}); // end of extend

// {{{
// show/hide resizer handles override
Ext.override(Ext.Resizable, {
	
	/**
		* Hide resizer handles
		*/
	hideHandles: function() {
		this.showHandles(false);
	} // end of function hideHandles

	/**
		* Show resizer handles
		*
		* @param {Boolean} show (true = show, false = hide)
		*/
	, showHandles: function(show) {
		show = (false === show ? false : true);
		var pos;
		for(var p in Ext.Resizable.positions) {
			pos = Ext.Resizable.positions[p];
			if(this[pos]) {
				this[pos].el.setStyle('display', show ? '' : 'none');
			}
		}
	} // end of function showHandles
// }}}

});

// end of file


// vim: ts=2:sw=2:nu:fdc=4:nospell

// Create user extensions namespace (Ext.ux)
Ext.namespace('Ext.ux');

/**
  * Ext.ux.Accordion Extension Class
	*
	* @author  Ing. Jozef Sakalos
	* @version $Id: Ext.ux.Accordion.js 152 2007-08-21 17:46:03Z jozo $
  *
  * @class Ext.ux.Accordion
  * @extends Ext.ContentPanel
  * @constructor
  * @param {String/HTMLElement/Element} el The container element for this panel
  * @param {String/Object} config A string to set only the title or a config object
	* @cfg {Boolean} animate global animation flag for all panels. (defaults to true)
	* @cfg {Boolean} boxWrap set to true to wrap wrapEl the body is child of (defaults to false)
	* @cfg {Boolean} draggable set to false to disallow panels dragging (defaults to true)
	* @cfg {Boolean} fitHeight set to true if you use fixed height dock
	* @cfg {Boolean} forceOrder set to true if to disable reordering of panels (defaults to false)
	* @cfg {Boolean} independent true to make panels independent (defaults to false)
	* @cfg {Integer} initialHeight Initial height to set box to (defaults to 0)
	* @cfg {Boolean} keepState Set to false to exclude this accordion from state management (defaults to true)
	* @cfg {Boolean} monitorWindowResize if true panels are moved to 
	*  viewport if window is small (defaults to true)
	* @cfg {Boolean} resizable global resizable flag for all panels (defaults to true)
	* @cfg {Boolean} undockable true to allow undocking of panels (defaults to true)
	* @cfg {Boolean} useShadow global useShadow flag for all panels. (defaults to true)
	* @cfg {Element/HTMLElement/String} wrapEl Element to wrap with nice surrounding
  */
Ext.ux.Accordion = function(el, config) {
	
	// call parent constructor
	Ext.ux.Accordion.superclass.constructor.call(this, el, config);

	// create collection for panels
	this.items = new Ext.util.MixedCollection();

	// assume no panel is expanded
	this.expanded = null;

	// {{{
	// install event handlers
	this.on({

		// {{{
		// runs before expansion. Triggered by panel's beforeexpand event
		beforeexpand: {
			  scope: this
			, fn: function(panel) {
					// raise panel above others
					if(!panel.docked) {
						this.raise(panel);
					}

					// set fixed height
					panel.autoSize();
//					var panelBodyHeight;
//					if(this.fitHeight && panel.docked) {
//						panelBodyHeight = this.getPanelBodyHeight();
//						if(panelBodyHeight) {
//							panel.body.setHeight(panelBodyHeight);
//						}
//					}

					if(panel.docked) {
						this.expandCount++;
						this.expanding = true;
//						this.setDockScroll(false);
					}

					// don't collapse others if independent or not docked
					if(this.independent || !panel.docked) {
						return this;
					}

					// collapse expanded panel
					if(this.expanded && this.expanded.docked) {
						this.expanded.collapse();
					}

					// remember this panel as expanded
					this.expanded = panel;
		}}
		// }}}
		// {{{
		// runs before panel collapses. Triggered by panel's beforecollapse event
		, beforecollapse: {
			  scope: this
			, fn: function(panel) {

				// raise panel if not docked
				if(!panel.docked) {
					this.raise(panel);
				}
				return this;
		}}
		// }}}
		// {{{
		// runs on when panel expands (before animation). Triggered by panel's expand event
		, expand: {
			  scope: this
			, fn: function(panel) {
				if(this.hideOtherOnExpand) {
					this.hideOther(panel);
				}
				this.fireEvent('panelexpand', panel);
		}}
		// }}}
		// {{{
		// runs on when panel collapses (before animation). Triggered by panel's collapse event
		, collapse: {
		 	  scope: this
			, fn: function(panel) {
				this.fireEvent('panelcollapse', panel);
		}}
		// }}}
		// {{{
		// runs on when animation is completed. Triggered by panel's animationcompleted event
		, animationcompleted: {
			scope: this
			, fn: function(panel) {
				var box = panel.el.getBox();
				this.expandCount = (this.expandCount && this.expanding) ? --this.expandCount : 0;
				if((0 === this.expandCount) && this.expanding) {
//					this.setDockScroll(true);
					this.expanding = false;
				}
				if(this.hideOtherOnExpand) {
					if(panel.collapsed && panel.docked) {
						this.showOther(panel);
					}
//					else if(panel.docked) {
//						this.hideOther(panel);
//					}
				}
//				this.fireEvent('panelbox', panel, box);
		}}
		// }}}
		// {{{
		// runs when panel is pinned. Triggered by panel's pinned event
		, pinned: {
			  scope: this
			, fn: function(panel, pinned) {
				if(!pinned) {
					if(panel.collapseOnUnpin) {
						panel.collapse();
					}
					else if(!this.independent) {
						this.items.each(function(p) {
							if(p !== panel && p.docked && !p.pinned) {
								p.collapse();
							}
						});
						this.expanded = panel;
					}
				}
				if(this.hideOtherOnExpand) {
					if(panel.docked && pinned) {
						this.showOther(panel);
					}
				}
				this.fireEvent('panelpinned', panel, pinned);
		}}
		// }}}

		, destroy: {
			scope:this
			, fn: function(panel) {
				this.items.removeKey(panel.id);
				this.updateOrder();
		}}
	});
	// }}}
	// {{{
	// add events
	this.addEvents({
		/**
			* Fires when a panel of the dock is collapsed
			* @event panelcollapse
			* @param {Ext.ux.InfoPanel} panel
			*/
		panelcollapse: true

		/**
			* Fires when a panel of the dock is expanded
			* @event panelexpand
			* @param {Ext.ux.InfoPanel} panel
			*/
		, panelexpand: true

		/**
			* Fires when a panel of the dock is pinned
			* @event panelpinned
			* @param {Ext.ux.InfoPanel} panel
			* @param {Boolean} pinned true if panel was pinned false if unpinned
			*/
		, panelpinned: true

		/**
			* Fires when the independent state of dock changes
			* @event independent
			* @param {Ext.ux.Accordion} this
			* @param {Boolean} independent New independent state
			*/
		, independent: true

		/**
			* Fires when the order of panel is changed
			* @event orderchange
			* @param {Ext.ux.Accordion} this
			* @param {Array} order New order array
			*/
		, orderchange: true

		/**
			* Fires when the undockable state of dock changes
			* @event undockable
			* @param {Ext.ux.Accordion} this
			* @param {Array} undockable New undockable state
			*/
		, undockable: true

		/**
			* Fires when a panel is undocked
			* @event panelundock
			* @param {Ext.ux.InfoPanel} panel
			* @param {Object} box Position and size object
			*/
		, panelundock: true

		/**
			* Fires when a panel is undocked
			* @event paneldock
			* @param {Ext.ux.InfoPanel} panel
			*/
		, paneldock: true

		/**
			* Fires when a panel box is changed, e.g. after dragging
			* @event panelbox
			* @param {Ext.ux.InfoPanel} panel
			* @param {Object} box Position and size object
			*/
		, panelbox: true
		
		/**
			* Fires when useShadow status changes
			* @event useshadow
			* @param {Ext.ux.Accordion} this
			* @param {Boolean} shadow Use shadow (for undocked panels) flag
			*/
		, useshadow: true

		/**
			* Fires before the panel is detached from this accordion. Return false to cancel the detach
			* @event beforedetach
			* @param {Ext.ux.Accordion} this
			* @param {Ext.ux.InfoPanel} panel being detached
			*/
		, beforedetach: true

		/**
			* Fires after the panel has been detached from this accordion
			* @event detach
			* @param {Ext.ux.Accordion} this
			* @param {Ext.ux.InfoPanel} panel detached panel
			*/
		, detach: true

		/**
			* Fires before the panel is attached from this accordion. Return false to cancel the attach
			* @event beforeattach
			* @param {Ext.ux.Accordion} this
			* @param {Ext.ux.InfoPanel} panel being attached
			*/
		, beforeattach: true

		/**
			* Fires after the panel is attached to this accordion
			* @event attach
			* @param {Ext.ux.Accordion} this
			* @param {Ext.ux.InfoPanel} panel attached panel
			*/
		, attach: true

	});
	// }}}

	// setup body
	this.body = Ext.get(this.body) || this.el;
	this.resizeEl = this.body;
	this.id = this.id || this.body.id;
	this.body.addClass('x-dock-body');

	// setup desktop
	this.desktop = Ext.get(this.desktop || document.body);
	//this.desktop = this.desktop.dom || this.desktop;

	// setup fixed hight
	this.wrapEl = Ext.get(this.wrapEl);
	if(this.fitHeight) {
		this.body.setStyle('overflow', 'hidden');
//		this.bodyHeight = this.initialHeight || this.body.getHeight();
		this.body.setHeight(this.initialHeight || this.body.getHeight());
		if(this.boxWrap && this.wrapEl) {
			this.wrapEl.boxWrap();
		}
	}

	// watch window resize
	if(this.monitorWindowResize) {
		Ext.EventManager.onWindowResize(this.adjustViewport, this);
	}

	// create drop zone for panels
	this.dd = new Ext.dd.DropZone(this.body.dom, {
		ddGroup: this.ddGroup || 'dock-' + this.id 
	});

	Ext.ux.AccordionManager.add(this);

}; // end of constructor

// extend
Ext.extend(Ext.ux.Accordion, Ext.ContentPanel, {

	// {{{
	// defaults
	independent: false
	, undockable: true
	, useShadow: true
	, boxWrap: false
	, fitHeight: false
	, initialHeight: 0
	, animate: true // global animation flag
	, expandCount: 0
	, expanding: false
	, monitorWindowResize: true
	, resizable: true // global resizable flag
	, draggable: true // global draggable flag
	, forceOrder: false
	, keepState: true
	, hideOtherOnExpand: false
	// }}}
	// {{{
	/**
		* Adds the panel to Accordion
		* @param {Ext.ux.InfoPanel} panel Panel to add
		* @return {Ext.ux.InfoPanel} added panel
		*/
	, add: function(panel) {

		// append panel to body
		this.body.appendChild(panel.el);

		this.attach(panel);

		// panel dragging 
		if(undefined === panel.draggable && this.draggable) {
			panel.draggable = true;
			panel.dd = new Ext.ux.Accordion.DDDock(panel, this.ddGroup || 'dock-' + this.id, this);
		}

		// panel resizing
		if(undefined === panel.resizable && this.resizable) {
			panel.resizable = true;
//			panel.setResizable(true);
		}

		// panel shadow
		panel.useShadow = undefined === panel.useShadow ? this.useShadow : panel.useShadow;
		panel.setShadow(panel.useShadow);
		if(panel.shadow) {
			panel.shadow.hide();
		}

		// panel animation
		panel.animate = undefined === panel.animate ? this.animate : panel.animate;

		// z-index for panel
		panel.zindex = Ext.ux.AccordionManager.getNextZindex();

		panel.docked = true;
		panel.desktop = this.desktop;

		if(false === panel.collapsed) {
			panel.collapsed = true;
			panel.expand(true);
		}
		return panel;

	}
	// }}}
	// {{{
	/**
		* attach panel to this accordion
		* @param {Ext.ux.InfoPanel} panel panel to attach
		* @return {Ext.ux.Accordion} this
		*/
	, attach: function(panel) {

		// fire beforeattach event
		if(false === this.fireEvent('beforeattach', this, panel)) {
			return this;
		}

		// add panel to items
		this.items.add(panel.id, panel);

		// install event handlers
		this.installRelays(panel);
		panel.bodyClickDelegate = this.onClickPanelBody.createDelegate(this, [panel]);
		panel.body.on('click', panel.bodyClickDelegate);

		// set panel dock
		panel.dock = this;

		// add docked class to panel body
		panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');

		// repair panel height
		panel.autoSize();
		if(this.fitHeight) {
			this.setPanelHeight(panel);
		}

		// fire attach event
		this.fireEvent('attach', this, panel);

		return this;
	}
	// }}}
	// {{{
	/**
		* detach panel from this accordion
		* @param {Ext.ux.InfoPanel} panel to detach
		* @return {Ext.ux.Accordion} this
		*/
	, detach: function(panel) {

		// fire beforedetach event
		if(false === this.fireEvent('beforedetach', this, panel)) {
			return this;
		}

		// unhook events from panel
		this.removeRelays(panel);
		panel.body.un('click', panel.bodyClickDelegate);

		// remove panel from items
		this.items.remove(panel);
		panel.dock = null;

		// remove docked class from panel body
		panel.body.replaceClass('x-dock-panel-body-docked', 'x-dock-panel-body-undocked');

		// repair expanded property
		if(this.expanded === panel) {
			this.expanded = null;
		}

		// repair panel height
		panel.autoSize();
		if(this.fitHeight) {
			this.setPanelHeight();
		}

		// fire detach event
		this.fireEvent('detach', this, panel);

		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally to raise panel above others
		* @param {Ext.ux.InfoPanel} panel Panel to raise
		* @return {Ext.ux.InfoPanel} panel Panel that has been raised
		*/
	, raise: function(panel) {
		return Ext.ux.AccordionManager.raise(panel);
	}
	// }}}
	// {{{
	/**
		* Resets the order of panels within the dock
		*
		* @return {Ext.ux.Accordion} this
		*/
	, resetOrder: function() {
		this.items.each(function(panel) {
			if(!panel.docked) {
				return;
			}
			this.body.appendChild(panel.el);
		}, this);
		this.updateOrder();
		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally to update the order variable after dragging
		*/
	, updateOrder: function() {
		var order = [];
		var titles = this.body.select('div.x-layout-panel-hd');
		titles.each(function(titleEl) {
			order.push(titleEl.dom.parentNode.id);
		});
		this.order = order;
		this.fireEvent('orderchange', this, order);
	}
	// }}}
	// {{{
	/**
		* Returns array of panel ids in the current order
		* @return {Array} order of panels
		*/
	, getOrder: function() {
		return this.order;
	}
	// }}}
	// {{{
	/**
		* Set the order of panels
		* @param {Array} order Array of ids of panels in required order.
		* @return {Ext.ux.Accordion} this
		*/
	, setOrder: function(order) {
		if('object' !== typeof order || undefined === order.length) {
			throw "setOrder: Argument is not array.";
		}
		var panelEl, dock, panelId, panel;
		for(var i = 0; i < order.length; i++) {
			panelId = order[i];
			dock = Ext.ux.AccordionManager.get(panelId);
			if(dock && dock !== this) {
				panel = dock.items.get(panelId);
				dock.detach(panel);
				this.attach(panel);
			}
			panelEl = Ext.get(panelId);
			if(panelEl) {
				this.body.appendChild(panelEl);
			}
		}
		this.updateOrder();
		return this;
	}
	// }}}
	// {{{
	/**
		* Collapse all docked panels
		* @param {Boolean} alsoPinned true to first unpin then collapse
		* @param {Ext.ux.InfoPanel} except This panel will not be collapsed.
		* @return {Ext.ux.Accordion} this
		*/
	, collapseAll: function(alsoPinned, except) {
		this.items.each(function(panel) {
			if(panel.docked) {
				panel.pinned = alsoPinned ? false : panel.pinned;
				if(!except || panel !== except) {
					panel.collapse();
				}
			}
		}, this);
		return this;
	}
	// }}}
	// {{{
	/**
		* Expand all docked panels in independent mode
		* @return {Ext.ux.Accordion} this
		*/
	, expandAll: function() {
		if(this.independent) {
			this.items.each(function(panel) {
				if(panel.docked && panel.collapsed) {
					panel.expand();
				}
			}, this);
		}
	}
	// }}}
	// {{{
	/**
		* Called internally while dragging and by state manager
		* @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
		* @box {Object} box coordinates with target position and size
		* @return {Ext.ux.Accordion} this
		*/
	, undock: function(panel, box) {

		// get panel if necessary
		panel = 'string' === typeof panel ? this.items.get(panel) : panel;

		// proceed only if we have docked panel and in undockable mode
		if(panel && panel.docked && this.undockable) {

			// sanity check
			if(box.x < 0 || box.y < 0) {
				return this;
			}

			// todo: check this
//			if(panel.collapsed) {
//				box.height = panel.lastHeight || panel.maxHeight || box.height;
//			}

			// move the panel in the dom (append to desktop)
			this.desktop.appendChild(panel.el.dom);

			// adjust panel visuals
			panel.el.applyStyles({
				position:'absolute'
				, 'z-index': panel.zindex
			});
			panel.body.replaceClass('x-dock-panel-body-docked', 'x-dock-panel-body-undocked');

			// position the panel
			panel.setBox(box);

			// reset docked flag
			panel.docked = false;

			// hide panel shadow (will be shown by raise)
			if(panel.shadow) {
				panel.shadow.hide();
			}

			// raise panel above others
			this.raise(panel);

			panel.autoSize();

			if(panel === this.expanded) {
				this.expanded = null;
			}

			// set the height of a docked expanded panel
			this.setPanelHeight(this.expanded);

			// enable resizing and scrolling
			panel.setResizable(!panel.collapsed);

			// remember size of the undocked panel
			panel.lastWidth = box.width;
//			panel.lastHeight = box.height;

			// fire panelundock event
			this.fireEvent('panelundock', panel, {x:box.x, y:box.y, width:box.width, height:box.height});

//			this.updateOrder();
		}

		return this;
	}
	// }}}
	// {{{
	/**
		* Called internally while dragging 
		* @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
		* @param {String} targetId id of panel after which this panel will be docked
		* @return {Ext.ux.Accordion} this
		*/
	, dock: function(panel, targetId) {

		// get panel if necessary
		panel = 'string' === typeof panel ? this.items.get(panel) : panel;

		// proceed only if we have a docked panel
		var dockWidth, newTargetId, panelHeight, idx, i, targetPanel;
		if(panel && !panel.docked) {

			// find correct target if order is forced
			if(this.forceOrder) {
				idx = this.items.indexOf(panel);
				for(i = idx + 1; i < this.items.getCount(); i++) {
					targetPanel = this.items.itemAt(i);
					if(targetPanel.docked) {
						newTargetId = targetPanel.id;
						break;
					}
				}
				targetId = newTargetId || this.id;
			}

			// remember width and height
			if(!panel.collapsed) {
//				panel.lastWidth = panel.el.getWidth();
//				panel.lastHeight = panel.el.getHeight();
				if(!this.independent && this.expanded) {
					this.expanded.collapse();
				}
				this.expanded = panel;
			}

			dockWidth = this.body.getWidth(true);

			// move the panel element in the dom
			if(targetId && (this.body.id !== targetId)) {
				panel.el.insertBefore(Ext.fly(targetId));
			}
			else {
				panel.el.appendTo(this.body);
			}

			// set docked flag
			panel.docked = true;

			// adjust panel visuals
			panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
			panel.el.applyStyles({
				top:''
				, left:''
				, width:''
//				, height:''
				, 'z-index':''
				, position:'relative'
				, visibility:''
			});
			panel.body.applyStyles({width: Ext.isIE ? dockWidth + 'px' : '', height:''});

			panel.autoSize();
//			if(!this.fitHeight) {
//				panelHeight = panel.fixedHeight || panel.maxHeight;
//				if(panelHeight) {
//					panel.setHeight(panelHeight);
//				}
//			}

			// disable resizing and shadow
			panel.setResizable(false);
			if(panel.shadow) {
				panel.shadow.hide();
			}

			// set panel height (only if this.fitHeight = true)
			this.setPanelHeight(panel.collapsed ? this.expanded : panel);

			// fire paneldock event
			this.fireEvent('paneldock', panel);

//			this.updateOrder();
		}

		return this;
	}
	// }}}
	// {{{
	/**
		* Moves panel from this dock (accordion) to another
		* @param {Ext.ux.InfoPanel} panel Panel to move
		* @param {Ext.ux.Accordion} targetDock Dock to move to
		*/
	, moveToDock: function(panel, targetDock) {
		this.detach(panel);
		targetDock.attach(panel);
		panel.docked = false;
		targetDock.dock(panel);
		this.setPanelHeight();
		this.updateOrder();
		targetDock.updateOrder();
	}
	// }}}
	// {{{
	/**
		* Sets the independent mode
		* @param {Boolean} independent set to false for normal mode
		* @return {Ext.ux.Accordion} this
		*/
	, setIndependent: function(independent) {
		this.independent = independent ? true : false;
		this.fireEvent('independent', this, independent);
		return this;
	}
	// }}}
	// {{{
	/**
		* Sets the undockable mode 
		* If undockable === true all undocked panels are docked and collapsed (except pinned)
		* @param {Boolean} undockable set to true to not allow undocking
		* @return {Ext.ux.Accordion} this
		*/
	, setUndockable: function(undockable) {
		this.items.each(function(panel) {

			// dock and collapse (except pinned) all undocked panels if not undockable
			if(!undockable && !panel.docked) {
				this.dock(panel);
				if(!this.independent && !panel.collapsed && !panel.pinned) {
					panel.collapse();
				}
			}

			// refresh dragging constraints
			if(panel.docked && panel.draggable) {
				panel.dd.constrainTo(this.body, 0, false);
				panel.dd.clearConstraints();
				if(undockable) {
					panel.constrainToDesktop();
				}
				else {
					panel.dd.setXConstraint(0,0);
				}
			}
		}, this);

		// set the flag and fire event
		this.undockable = undockable;
		this.fireEvent('undockable', this, undockable);
		return this;
	}
	// }}}
	// {{{
	/**
		* Sets the shadows for all panels
		* @param {Boolean} shadow set to false to disable shadows
		* @return {Ext.ux.Accordion} this
		*/
	, setShadow: function(shadow) {
		this.items.each(function(panel) {
			panel.useShadow = shadow;
			panel.setShadow(false);
			if(!panel.docked) {
				panel.setShadow(shadow);
			}
		});
		this.useShadow = shadow;
		this.fireEvent('useshadow', this, shadow);
		return this;
	}
	// }}}
// {{{
	/**
		* Called when user clicks the panel body
		* @param {Ext.ux.InfoPanel} panel
		*/
	, onClickPanelBody: function(panel) {
		if(!panel.docked) {
			this.raise(panel);
		}
	}
	// }}}
	// {{{
	/**
		* Called internally for fixed height docks to get current height of panel(s)
		*/
	, getPanelBodyHeight: function() {
			var titleHeight = 0;
			this.items.each(function(panel) {
				titleHeight += panel.docked ? panel.titleEl.getHeight() : 0;
			});
			this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb') + 1;
//			this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb');
			return this.panelBodyHeight;
	}
	// }}}
	// {{{
	/**
		* Sets the height of panel body 
		* Used with fixed height (fitHeight:true) docs
		* @param {Ext.ux.InfoPanel} panel (defaults to this.expanded)
		* @return {Ext.ux.Accordion} this
		*/
	, setPanelHeight: function(panel) {
		panel = panel || this.expanded;
		if(this.fitHeight && panel && panel.docked) {
			panel.body.setHeight(this.getPanelBodyHeight());
			panel.setHeight(panel.getHeight());
		}
		return this;
	}
	// }}}
	// {{{
	/**
		* Constrains the dragging of panels do the desktop
		* @return {Ext.ux.Accordion} this
		*/
	, constrainToDesktop: function() {
		this.items.each(function(panel) {
			panel.constrainToDesktop();
		}, this);
		return this;
	}
	// }}}
	// {{{
	/** 
		* Clears dragging constraints of panels
		* @return {Ext.ux.Accordion} this
		*/
	, clearConstraints: function() {
		this.items.each(function(panel) {
			panel.dd.clearConstraints();
		});
	}
	// }}}
	// {{{
	/**
		* Shows all panels
		* @param {Boolean} show (optional) if false hides the panels instead of showing
		* @param {Boolean} alsoUndocked show also undocked panels (defaults to false)
		* @return {Ext.ux.Accordion} this
		*/
	, showAll: function(show, alsoUndocked) {
		show = (false === show ? false : true);
		this.items.each(function(panel) {
			panel.show(show, alsoUndocked);
		});
		return this;
	}
	// }}}

	, showOther: function(panel, show, alsoPinned) {
		show = (false === show ? false : true);
		this.items.each(function(p) {
				if(p === panel || (p.pinned && !alsoPinned)) {
					return;
				}
				if(show) {
					p.show();		
				}
				else {
					p.hide();
				}
		});
	}

	, hideOther: function(panel, alsoPinned) {
		this.showOther(panel, false, alsoPinned);
	}

	// {{{
	/**
		* Hides all panels
		* @param {Boolean} alsoUndocked hide also undocked panels (defaults to false)
		* @return {Ext.ux.Accordion} this
		*/
	, hideAll: function(alsoUndocked) {
		return this.showAll(false, alsoUndocked);
	}
	// }}}
	// {{{
	/**
		* Called internally to disable/enable scrolling of the dock while animating
		* @param {Boolean} enable true to enable, false to disable
		* @return {void}
		* @todo not used at present - revise
		*/
	, setDockScroll: function(enable) {
		if(enable && !this.fitHeight) {
			this.body.setStyle('overflow','auto');
		}
		else {
			this.body.setStyle('overflow','hidden');
		}
	}
	// }}}
	// {{{
	/**
		* Set Accordion size
		* Overrides ContentPanel.setSize
		*
		* @param {Integer} w width
		* @param {Integer} h height
		* @return {Ext.ux.Accordion} this
		*/
	, setSize: function(w, h) {
		// call parent's setSize
		Ext.ux.Accordion.superclass.setSize.call(this, w, h);
//		this.body.setHeight(h);
		this.setPanelHeight();

		return this;
	}
	// }}}
	// {{{
	/** 
		* Called as windowResize event handler
		*
		* @todo: review
		*/
	, adjustViewport: function() {
		var viewport = this.desktop.dom === document.body ? {} : Ext.get(this.desktop).getBox();

		viewport.height = 
			this.desktop === document.body 
			? window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
			: viewport.height
		;

		viewport.width = 
			this.desktop === document.body 
			? window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
			: viewport.width
		;

		viewport.x = this.desktop === document.body ? 0 : viewport.x;
		viewport.y = this.desktop === document.body ? 0 : viewport.y;

		this.items.each(function(panel) {
			if(!panel.docked) {
				panel.moveToViewport(viewport);
			}
		});

	}
	// }}}
	// {{{
	/**
		* private - called internally to create relay event function
		* @param {String} ename event name to relay
		* @return {Function} relay event function
		*/
	, createRelay: function(ename) {
		return function() {
			return this.fireEvent.apply(this, Ext.combine(ename, Array.prototype.slice.call(arguments, 0)));
		};
	}
	// }}}
	// {{{
	/**
		* Array of event names to relay
		*/
	, relayedEvents: [
		'beforecollapse'
		, 'collapse'
		, 'beforeexpand'
		, 'expand'
		, 'animationcompleted'
		, 'pinned'
		, 'boxchange'
		, 'destroy'
	]
	// }}}
	// {{{
	/**
		* private - called internaly to install event relays on panel
		* @param {Ext.ux.InfoPanel} panel panel to install events on
		*/
	, installRelays: function(panel) {
		panel.relays = {};
		var ename, fn;
		for(var i = 0; i < this.relayedEvents.length; i++) {
			ename = this.relayedEvents[i];
			fn = this.createRelay(ename);
			panel.relays[ename] = fn;
			panel.on(ename, fn, this);
		}
	}
	// }}}
	// {{{
	/**
		* private - called internaly to remove installed relays
		* @param {Ext.ux.InfoPanel} panel panel to remove relays from
		*/
	, removeRelays: function(panel) {
		for(var ename in panel.relays) {
			panel.un(ename, panel.relays[ename], this);
		}
		panel.relays = {};
	}
	// }}}
	// {{{
	/**
		* Removes and destroys panel
		* @param {String/InfoPanel} panel Panel object or id
		*/
	, remove: function(panel) {
		panel = this.items.get(panel.id || panel);
		if(panel) {
			this.detach(panel);
			panel.destroy();
		}
	}
	// }}}
	// {{{
	/**
		* Removes and destroys all panels
		*/
	, removeAll: function() {
		this.items.each(function(panel) {
				this.remove(panel);
		}, this);
	}
	// }}}
	// {{{
	/**
		* Destroys Accrodion
		*/
	, destroy: function() {
		this.removeAll();
		Ext.ux.Accordion.superclass.destroy.call(this);
	}
	// }}}

}); // end of extend

// {{{
// {{{
/**
	* @class Ext.ux.Accordion.DDDock
	* @constructor
	* @extends Ext.dd.DDProxy
	* @param {Ext.ux.InfoPanel} panel Panel the dragging object is created for
	* @param {String} group Only elements of same group interact
	* @param {Ext.ux.Accordion} dock Place where panels are docked/undocked
	*/
Ext.ux.Accordion.DDDock = function(panel, group, dock) {

	// call parent constructor
	Ext.ux.Accordion.DDDock.superclass.constructor.call(this, panel.el.dom, group);

	// save panel and dock references for use in methods
	this.panel = panel;
	this.dock = dock;

	// drag by grabbing the title only
	this.setHandleElId(panel.titleEl.id);

	// move only in the dock if undockable
	if(false === dock.undockable) {
		this.setXConstraint(0, 0);
	}

	// init internal variables
	this.lastY = 0;

	//this.DDM.mode = Ext.dd.DDM.INTERSECT;
	this.DDM.mode = Ext.dd.DDM.POINT;

}; // end of constructor
// }}}

// extend
Ext.extend(Ext.ux.Accordion.DDDock, Ext.dd.DDProxy, {

	// {{{
	/**
		* Default DDProxy startDrag override
		* Saves some variable for use by other methods 
		* and creates nice dragging proxy (ghost)
		*
		* Passed x, y arguments are not used
		*/
	startDrag: function(x, y) {

		this.createIframeMasks();

		this.lastMoveTarget = null;

		// create nice dragging ghost
		this.createGhost();

		// get srcEl (the original) and dragEl (the ghost)
		var srcEl = Ext.get(this.getEl());
		var dragEl = Ext.get(this.getDragEl());

		// refresh constraints
		this.panel.constrainToDesktop();
		var dragHeight, rightC, bottomC;
		if(this.panel.dock.undockable) {
			if(this.panel.collapsed) {
				dragHeight = this.panel.titleEl.getHeight();
			}
			else {
				dragHeight = dragEl.getHeight();
				dragHeight = dragHeight <= this.panel.titleEl.getHeight() ? srcEl.getHeight() : dragHeight;
			}

			rightC = this.rightConstraint + srcEl.getWidth() - dragEl.getWidth();
			bottomC = this.bottomConstraint + srcEl.getHeight() - dragHeight;
			this.setXConstraint(this.leftConstraint, rightC);
			this.setYConstraint(this.topConstraint, bottomC);
		}
		else {
			if(this.panel.docked) {
				this.setXConstraint(0, 0);
			}
		}

		// hide dragEl (will be shown by onDrag)
		dragEl.hide();

		// raise panel's "window" above others
		if(!this.panel.docked) {
			this.panel.dock.raise(this.panel);
		}

		// hide panel's shadow if any
		this.panel.setShadow(false);

		// clear visibility of panel's body (was setup by animations)
		this.panel.body.dom.style.visibility = '';

		// hide source panel if undocked
		if(!this.panel.docked) {
//			srcEl.hide();
			srcEl.setDisplayed(false);
			dragEl.show();
		}

	} // end of function startDrag
	// }}}
	// {{{
	/**
		* Called internally to create nice dragging proxy (ghost)
		*/
	, createGhost: function() {

		// get variables
		var srcEl = Ext.get(this.getEl());
		var dragEl = Ext.get(this.getDragEl());
		var panel = this.panel;
		var dock = panel.dock;

		// adjust look of ghost
		var am = Ext.ux.AccordionManager;
		dragEl.addClass('x-dock-panel-ghost');
		dragEl.applyStyles({border:'1px solid #84a0c4','z-index': am.zindex + am.zindexInc});

		// set size of ghost same as original
		dragEl.setBox(srcEl.getBox());
		if(panel.docked) {
			if(panel.lastWidth && dock.undockable) {
				dragEl.setWidth(panel.lastWidth);
			}
			if(!panel.collapsed && dock.undockable && (panel.lastHeight > panel.titleEl.getHeight())) {
				dragEl.setHeight(panel.lastHeight);
			}
		}

		// remove unnecessary text nodes from srcEl
		srcEl.clean();

		// setup title
		var dragTitleEl = Ext.DomHelper.append(dragEl, {tag:'div'}, true);
		dragTitleEl.update(srcEl.dom.firstChild.innerHTML);
		dragTitleEl.dom.className = srcEl.dom.firstChild.className;
		if(panel.collapsed && Ext.isIE) {
			dragTitleEl.dom.style.borderBottom = "0";
		}

	} // end of function createGhost
	// }}}
	// {{{
	/**
		* Default DDProxy onDragOver override
		* It is called when dragging over a panel
		* or over the dock.body DropZone
		*
		* @param {Event} e not used
		* @param {String} targetId id of the target we're over
		*
		* Beware: While dragging over docked panels it's called
		* twice. Once for panel and once for DropZone
		*/
	, onDragOver: function(e, targetId) {

		// get panel element
		var srcEl = Ext.get(this.getEl());

		// get target panel and dock
		var targetDock = Ext.ux.AccordionManager.get(targetId);
		var targetPanel = targetDock ? targetDock.items.get(targetId) : this.panel;

		// setup current target for endDrag
		if(targetPanel) {
			this.currentTarget = targetPanel.id;
		}
		if(targetDock && !this.currentTarget) {
			this.currentTarget = targetDock.id;
		}

		// landing indicators
		if(targetPanel && targetPanel.docked && !this.panel.dock.forceOrder) {
			targetPanel.titleEl.addClass('x-dock-panel-title-dragover');
		}
		if(targetDock) {
			targetDock.body.addClass('x-dock-body-dragover');
		}
		if(this.panel.docked) {
			this.panel.titleEl.addClass('x-dock-panel-title-dragover');
		}

		// reorder panels in dock if we're docked too
		var targetEl;

		if(targetDock === this.panel.dock 
				&& targetPanel 
				&& targetPanel.docked 
				&& this.panel.docked 
				&& !this.panel.dock.forceOrder) {
			targetEl = targetPanel.el;

			if(targetPanel.collapsed || this.lastMoveTarget !== targetPanel) {
				if(this.movingUp) {
					srcEl.insertBefore(targetEl);
					this.lastMoveTarget = targetPanel;
				}
				else {
					srcEl.insertAfter(targetEl);
					this.lastMoveTarget = targetPanel;
				}
			}
			this.DDM.refreshCache(this.groups);
		}

	} // end of function onDragOver
	// }}}
	// {{{
	/**
		* called internally to attach this.panel to accordion
		* @param {Ext.ux.Accordion} targetDock the dock to attach the panel to
		*/
	, attachToDock: function(targetDock) {
		if(targetDock && this.panel.dock !== targetDock) {
			// detach panel
			this.panel.dock.detach(this.panel);

			// attach panel
			targetDock.attach(this.panel);

		}
	}
	// }}}
	// {{{
	/**
		* Called internally when cursor leaves a drop target
		* @param {Ext.Event} e
		* @param {String} targetId id of target we're leaving
		*/
	, onDragOut: function(e, targetId) {

		var targetDock = Ext.ux.AccordionManager.get(targetId);
		var targetPanel = targetDock ? targetDock.items.get(targetId) : this.panel;

		if(targetDock) {
			targetDock.body.removeClass('x-dock-body-dragover');
		}

		if(targetPanel) {
			targetPanel.titleEl.removeClass('x-dock-panel-title-dragover');
		}
		this.currentTarget = null;
	}
	// }}}
	// {{{
	/**
		* Default DDProxy onDrag override
		*
		* It's called while dragging
		* @param {Event} e used to get coordinates
		*/
	, onDrag: function(e) {

		// get source (original) and proxy (ghost) elements
		var srcEl = Ext.get(this.getEl());
		var dragEl = Ext.get(this.getDragEl());

		if(!dragEl.isVisible()) {
			dragEl.show();
		}

		var y = e.getPageY();

		this.movingUp = this.lastY > y;
		this.lastY = y;

	} // end of function onDrag
	// }}}
	// {{{
	/**
		* Default DDProxy endDrag override
		*
		* Called when dragging is finished
		*/
	, endDrag: function() {

		this.destroyIframeMasks();

		// get the source (original) and proxy (ghost) elements
		var srcEl = Ext.get(this.getEl());
		var dragEl = Ext.get(this.getDragEl());

		srcEl.setDisplayed(true);

		// get box and hide the ghost
		var box = dragEl.getBox();

		var sourceDock = this.panel.dock;
		var targetDock = Ext.ux.AccordionManager.get(this.currentTarget);
		var targetPanel = targetDock ? targetDock.items.get(this.currentTarget) : this.panel;
		var orderChanged = false;

		// remove any dragover classes from panel title and dock
		this.panel.titleEl.removeClass('x-dock-panel-title-dragover');
		this.dock.body.removeClass('x-dock-body-dragover');
		if(targetDock) {
			targetDock.items.each(function(panel) {
				panel.titleEl.removeClass('x-dock-panel-title-dragover');
			});
		}

		// undock (docked panel dropped out of dock)
		if(!this.panel.dock.catchPanels && (this.panel.docked && !this.currentTarget && !targetDock) || (targetPanel && !targetPanel.docked)) {
			this.dock.undock(this.panel, box);
			orderChanged = true;
		}

		// dock undocked panel
		else if(!this.panel.docked) {
			this.attachToDock(targetDock);
			this.panel.dock.dock(this.panel, this.currentTarget);
			orderChanged = true;
		}

		// do nothing for panel moved over it's own dock
		// handling has already been done by onDragOver
		else if(this.panel.docked && (this.panel.dock === targetDock)) {
			// do nothing on purpose - do not remove
			orderChanged = true;
		}
		
		// dock panel to another dock
		else if(this.currentTarget || targetDock) {
			this.attachToDock(targetDock);
			if(targetDock) {
				targetDock.body.removeClass('x-dock-body-dragover');
				this.panel.docked = false;
				targetDock.dock(this.panel, this.currentTarget);
			}
			orderChanged = true;
		}

		// just free dragging
		if(!this.panel.docked) {
			this.panel.setBox(box);

			// let the state manager know the new panel position
			this.dock.fireEvent('panelbox', this.panel, {x:box.x, y:box.y, width:box.width, height:box.height});
		}

		// clear the ghost content, hide id and move it off screen
		dragEl.hide();
		dragEl.update('');
		dragEl.applyStyles({
			top:'-9999px'
			, left:'-9999px'
			, height:'0px'
			, width:'0px'
		});

		if(orderChanged) {
			sourceDock.updateOrder();
			if(targetDock && targetDock !== sourceDock) {
				targetDock.updateOrder();
			}
		}
		this.DDM.refreshCache(this.groups);

	} // end of function endDrag
	// }}}
	// {{{
	, createIframeMasks: function() {
		this.destroyIframeMasks();
		
		var masks = [];
		var iframes = Ext.get(document.body).select('iframe');
		iframes.each(function(iframe) {
			var mask = Ext.DomHelper.append(document.body, {tag:'div'}, true);
			mask.setBox(iframe.getBox());
			masks.push(mask);
		});
		this.iframeMasks = masks;
	}
	// }}}
	// {{{
	, destroyIframeMasks: function() {
		if(!this.iframeMasks || ! this.iframeMasks.length) {
			return;
		}
		for(var i = 0; i < this.iframeMasks.length; i++) {
			this.iframeMasks[i].remove();
		}
		this.iframeMasks = [];
	}
	// }}}

});
// }}}
// {{{
/**
	* Private class for keeping and restoring state of the Accordion
	*/
Ext.ux.AccordionStateManager = function() {
	this.state = { docks:{}, panels:{} };
};

Ext.ux.AccordionStateManager.prototype = {
	init: function(provider) {

		// save state provider
		this.provider = provider;
//		var state = provider.get('accjs-state');
//		if(state) {
//			this.state = state;
//		}
		state = this.state;

		var am = Ext.ux.AccordionManager;
		var dockState;

		// {{{
		// docks loop
		am.each(function(dock) {
			if(false === dock.keepState) {
				return;
			}

			state.docks[dock.id] = provider.get('accjsd-' + dock.id);
			dockState = state.docks[dock.id];
			if(dockState) {

				// {{{
				// handle docks (accordions)
				if(dockState) {

					// {{{
					// restore order of panels
					if(dockState.order) {
						dock.setOrder(dockState.order);
					}
					// }}}
					// {{{
					// restore independent
					if(undefined !== dockState.independent) {
						dock.setIndependent(dockState.independent);
					}
					// }}}
					// {{{
					// restore undockable
					if(undefined !== dockState.undockable) {
						dock.setUndockable(dockState.undockable);
					}
					// }}}
					// {{{
					// restore useShadow
					if(undefined !== dockState.useShadow) {
						dock.setShadow(dockState.useShadow);
					}
					// }}}

				} // end of if(dockState)
				// }}}

			}

			// install event handlers on docks
			dock.on({
				orderchange: {scope:this, fn:this.onOrderChange}
				, independent: {scope:this, fn:this.onIndependent}
				, undockable: {scope:this, fn:this.onUndockable}
				, useshadow: {scope: this, fn: this.onUseShadow}
				, panelexpand: {scope: this, fn: this.onPanelCollapse}
				, panelcollapse: {scope: this, fn: this.onPanelCollapse}
				, panelpinned: {scope: this, fn: this.onPanelPinned}
				, paneldock: {scope: this, fn: this.onPanelUnDock}
				, panelundock: {scope: this, fn: this.onPanelUnDock}
				, boxchange: {scope: this, fn: this.onPanelUnDock}
				, panelbox: {scope: this, fn: this.onPanelUnDock}
			});
		}, this);
		// }}}

		// {{{
		// panels loop
		am.each(function(dock) {
			if(!dock.keepState) {
				return;
			}

			// panels within dock loop
			var panelState;
			dock.items.each(function(panel) {

				state.panels[panel.id] = provider.get('accjsp-' + panel.id);
				panelState = state.panels[panel.id];
				
				if(panelState) {

					// {{{
					// restore docked/undocked state
					if(undefined !== panelState.docked) {
						if(!panelState.docked) {
							if('object' === typeof panelState.box) {
								panel.docked = true;
								panel.dock.undock(panel, panelState.box);
							}
						}
					}
					// }}}
					// {{{
					// restore pinned state
					if(undefined !== panelState.pinned) {
						panel.pinned = panelState.pinned;
						if(panel.pinned) {
							panel.expand(true);
						}
						else {
							panel.updateVisuals();
						}
					}
					// }}}
					// {{{
					// restore collapsed/expanded state
					if(undefined !== panelState.collapsed) {
						if(panelState.collapsed) {
							panel.collapsed = false;
							panel.collapse(true);
						}
						else {
							panel.collapsed = true;
							panel.expand(true);
						}
					}
					// }}}

				}
			}, this); // end of panels within dock loop
		}, this); // end of docks loop
		// }}}


	}

	// event handlers
	// {{{
	, onOrderChange: function(dock, order) {
		if(false !== dock.keepState) {
			this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
			this.state.docks[dock.id].order = order;
			this.storeDockState(dock);
		}
	}
	// }}}
	// {{{
	, onIndependent: function(dock, independent) {
		if(false !== dock.keepState) {
			this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
			this.state.docks[dock.id].independent = independent;
			this.storeDockState(dock);
		}
	}
	// }}}
	// {{{
	, onUndockable: function(dock, undockable) {
		if(false !== dock.keepState) {
			this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
			this.state.docks[dock.id].undockable = undockable;
			this.storeDockState(dock);
		}
	}
	// }}}
	// {{{
	, onUseShadow: function(dock, shadow) {
		if(false !== dock.keepState) {
			this.state.docks[dock.id] = this.state.docks[dock.id] ? this.state.docks[dock.id] : {};
			this.state.docks[dock.id].useShadow = shadow;
			this.storeDockState(dock);
		}
	}
	// }}}
	// {{{
	, onPanelCollapse: function(panel) {
		if(panel.dock.keepState) {
			this.state.panels[panel.id] = this.state.panels[panel.id] || {};
			this.state.panels[panel.id].collapsed = panel.collapsed;
		}
		else {
			try {delete(this.state.panels[panel.id].collapsed);}
			catch(e){}
		}
		this.storePanelState(panel);
	}
	// }}}
	// {{{
	, onPanelPinned: function(panel, pinned) {
		if(panel.dock.keepState) {
			this.state.panels[panel.id] = this.state.panels[panel.id] || {};
			this.state.panels[panel.id].pinned = pinned;
		}
		else {
			try {delete(this.state.panels[panel.id].pinned);}
			catch(e){}
		}
		this.storePanelState(panel);
	}
	// }}}
	// {{{
	, onPanelUnDock: function(panel, box) {
		if(panel.dock.keepState) {
			this.state.panels[panel.id] = this.state.panels[panel.id] || {};
			this.state.panels[panel.id].docked = panel.docked ? true : false;
			this.state.panels[panel.id].box = box || null;
		}
		else {
			try {delete(this.state.panels[panel.id].docked);}
			catch(e){}
			try {delete(this.state.panels[panel.id].box);}
			catch(e){}
		}
//		console.log('onPanelUnDock: ', + panel.id);
		this.storePanelState(panel);
	}
	// }}}
	// {{{
	, storeDockState: function(dock) {
		this.provider.set.defer(700, this, ['accjsd-' + dock.id, this.state.docks[dock.id]]);
	}
	// }}}
	// {{{
	, storePanelState: function(panel) {
		this.provider.set.defer(700, this, ['accjsp-' + panel.id, this.state.panels[panel.id]]);
	}
	// }}}

}; // end of Ext.ux.AccordionManager.prototype
// }}}
// {{{
/**
	* Singleton to manage multiple accordions
	* @singleton
	*/
Ext.ux.AccordionManager = function() {

	// collection of accordions
	var items = new Ext.util.MixedCollection();

	// public stuff
	return {
		// starting z-index for panels
		zindex: 9999
		// z-index increment (2 as 1 is for shadow)
		, zindexInc: 2

		// {{{
		/**
			* increments (by this.zindexInc) this.zindex and returns new value
			* @return {Integer} next zindex value
			*/
		, getNextZindex: function() {
			this.zindex += this.zindexInc;
			return this.zindex;
		}
		// }}}
		// {{{
		/**
			* raises panel above others (in the same desktop)
			* Maintains z-index stack
			* @param {Ext.ux.InfoPanel} panel panel to raise
			* @return {Ext.ux.InfoPanel} panel panel that has been raised
			*/
		, raise: function(panel) {
			items.each(function(dock) {
				dock.items.each(function(p) {
					if(p.zindex > panel.zindex) {
						p.zindex -= this.zindexInc;
						p.el.applyStyles({'z-index':p.zindex});
						if(!p.docked) {
							p.setShadow(true);
						}
					}
				}, this);
			}, this);

			if(panel.zindex !== this.zindex) {
				panel.zindex = this.zindex;
				panel.el.applyStyles({'z-index':panel.zindex});
				if(panel.desktop.lastChild !== panel.el.dom) {
					panel.dock.desktop.appendChild(panel.el.dom);
				}
				if(!panel.docked) {
					panel.setShadow(true);
				}
			}

			return panel;
		}
		// }}}
		// {{{
		/**
			* Adds accordion to items
			* @param {Ext.ux.Accordion} acc accordion to add
			* @return {Ext.ux.Accordion} added accordion
			*/
		, add: function(acc) {
			items.add(acc.id, acc);
			return acc;
		}
		// }}}
		// {{{
		/**
			* get accordion by it's id or by id of some ot it's panels
			* @param {String} key id of accordion or panel
			* @return {Ext.ux.Accordion} or undefined if not found
			*/
		, get: function(key) {
			var dock = items.get(key);
			if(!dock) {
				items.each(function(acc) {
					if(dock) {
						return;
					}
					var panel = acc.items.get(key);
					if(panel) {
						dock = panel.dock;	
					}
				});
			}
			return dock;
		}
		// }}}
	// {{{
		/**
			* get panel by it's id
			* @param {String} key id of the panel to get
			* @return {Ext.ux.InfoPanel} panel found or null
			*/
		, getPanel: function(key) {
			var dock = this.get(key);
			return dock && dock.items ? this.get(key).items.get(key) : null;
		}
	// }}}
		// {{{
		/**
			* Restores state of dock and panels
			* @param {Ext.state.Provider} provider (optional) An alternate state provider
			*/
		, restoreState: function(provider) {
			if(!provider) {
				provider = Ext.state.Manager;
			}
			var sm = new Ext.ux.AccordionStateManager();
			sm.init(provider);

		}
		// }}}

		, each: function(fn, scope) {
			items.each(fn, scope);
		}

	}; // end of return

}();
// }}}

// end of file


var panalTab = function() {
	return {
		init : function(){
			// create accordion
			var acc = new Ext.ux.Accordion('acc-ct', {
				fitHeight: true
			})				
			// create panel 1
			var panel1 = acc.add(new Ext.ux.InfoPanel('panel-1', {
				title : 'บทสัมภาษณ์ประจำเดือน'
				,draggable: false
				,icon: iconInfo + 'icon_arrow02.gif'
			}));			
			// create panel 2
			var panel2 = acc.add(new Ext.ux.InfoPanel('panel-2', {
				title : 'บทสัมภาษณ์ย้อนหลัง'
				,draggable: false
				,autoScroll: true
				,icon: iconInfo + 'icon_arrow02.gif'
			}));			
			// create panel 3
			var panel3 = acc.add(new Ext.ux.InfoPanel('panel-3', {
				title : 'บทความต่างๆ'
				,draggable: false
				,collapsed: false
				,autoScroll: true
				,pinned: false
				,icon: iconInfo + 'icon_arrow02.gif'
			}));
	   }
 };
}();
Ext.EventManager.onDocumentReady(panalTab.init, panalTab, true);