# Yahoo! UI Library

### Container

Yahoo! UI Library > container > Panel.js (source view)

    /*
    Copyright (c) 2006, Yahoo! Inc. All rights reserved.
    Code licensed under the BSD License:
    http://developer.yahoo.net/yui/license.txt
    Version 0.12
    */

    /**
    * Panel is an implementation of Overlay that behaves like an OS window, with a draggable header and an optional close icon at the top right.
    * @namespace YAHOO.widget
    * @class Panel
    * @extends YAHOO.widget.Overlay
    * @constructor
    * @param {String}	el	The element ID representing the Panel <em>OR</em>
    * @param {HTMLElement}	el	The element representing the Panel
    * @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Panel. See configuration documentation for more details.
    */
    YAHOO.widget.Panel = function(el, userConfig) {
    	YAHOO.widget.Panel.superclass.constructor.call(this, el, userConfig);
    };

    YAHOO.extend(YAHOO.widget.Panel, YAHOO.widget.Overlay);

    /**
    * Constant representing the default CSS class used for a Panel
    * @property YAHOO.widget.Panel.CSS_PANEL
    * @static
    * @final
    * @type String
    */
    YAHOO.widget.Panel.CSS_PANEL = "panel";

    /**
    * Constant representing the default CSS class used for a Panel's wrapping container
    * @property YAHOO.widget.Panel.CSS_PANEL_CONTAINER
    * @static
    * @final
    * @type String
    */
    YAHOO.widget.Panel.CSS_PANEL_CONTAINER = "panel-container";

    /**
    * The Overlay initialization method, which is executed for Overlay and all of its subclasses. This method is automatically called by the constructor, and  sets up all DOM references for pre-existing markup, and creates required markup if it is not already present.
    * @method init
    * @param {String}	el	The element ID representing the Overlay <em>OR</em>
    * @param {HTMLElement}	el	The element representing the Overlay
    * @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Overlay. See configuration documentation for more details.
    */
    YAHOO.widget.Panel.prototype.init = function(el, userConfig) {
    	YAHOO.widget.Panel.superclass.init.call(this, el/*, userConfig*/);  // Note that we don't pass the user config in here yet because we only want it executed once, at the lowest subclass level

    	this.beforeInitEvent.fire(YAHOO.widget.Panel);

    	YAHOO.util.Dom.addClass(this.element, YAHOO.widget.Panel.CSS_PANEL);

    	this.buildWrapper();

    	if (userConfig) {
    		this.cfg.applyConfig(userConfig, true);
    	}

    	this.beforeRenderEvent.subscribe(function() {
    		var draggable = this.cfg.getProperty("draggable");
    		if (draggable) {
    			if (! this.header) {
    				this.setHeader("&nbsp;");
    			}
    		}
    	}, this, true);

    	var me = this;

    	this.showMaskEvent.subscribe(function() {
    		var checkFocusable = function(el) {
    			if (el.tagName == "A" || el.tagName == "BUTTON" || el.tagName == "SELECT" || el.tagName == "INPUT" || el.tagName == "TEXTAREA" || el.tagName == "FORM") {
    				if (! YAHOO.util.Dom.isAncestor(me.element, el)) {
    					YAHOO.util.Event.addListener(el, "focus", el.blur);
    					return true;
    				}
    			} else {
    				return false;
    			}
    		};

    		this.focusableElements = YAHOO.util.Dom.getElementsBy(checkFocusable);
    	}, this, true);

    	this.hideMaskEvent.subscribe(function() {
    		for (var i=0;i<this.focusableElements.length;i++) {
    			var el2 = this.focusableElements[i];
    			YAHOO.util.Event.removeListener(el2, "focus", el2.blur);
    		}
    	}, this, true);

    	this.beforeShowEvent.subscribe(function() {
    		this.cfg.refireEvent("underlay");
    	}, this, true);

    	this.initEvent.fire(YAHOO.widget.Panel);
    };

    /**
    * Initializes the custom events for Module which are fired automatically at appropriate times by the Module class.
    */
    YAHOO.widget.Panel.prototype.initEvents = function() {
    	YAHOO.widget.Panel.superclass.initEvents.call(this);

    	/**
    	* CustomEvent fired after the modality mask is shown
    	* @event showMaskEvent
    	*/
    	this.showMaskEvent = new YAHOO.util.CustomEvent("showMask");

    	/**
    	* CustomEvent fired after the modality mask is hidden
    	* @event hideMaskEvent
    	*/
    	this.hideMaskEvent = new YAHOO.util.CustomEvent("hideMask");

    	/**
    	* CustomEvent when the Panel is dragged
    	* @event dragEvent
    	*/
    	this.dragEvent = new YAHOO.util.CustomEvent("drag");
    };

    /**
    * Initializes the class's configurable properties which can be changed using the Panel's Config object (cfg).
    * @method initDefaultConfig
    */
    YAHOO.widget.Panel.prototype.initDefaultConfig = function() {
    	YAHOO.widget.Panel.superclass.initDefaultConfig.call(this);

    	// Add panel config properties //

    	/**
    	* True if the Panel should display a "close" button
    	* @config close
    	* @type Boolean
    	* @default true
    	*/
    	this.cfg.addProperty("close", { value:true, handler:this.configClose, validator:this.cfg.checkBoolean, supercedes:["visible"] } );

    	/**
    	* True if the Panel should be draggable
    	* @config draggable
    	* @type Boolean
    	* @default true
    	*/
    	this.cfg.addProperty("draggable", { value:true,	handler:this.configDraggable, validator:this.cfg.checkBoolean, supercedes:["visible"] } );

    	/**
    	* Sets the type of underlay to display for the Panel. Valid values are "shadow", "matte", and "none".
    	* @config underlay
    	* @type String
    	* @default shadow
    	*/
    	this.cfg.addProperty("underlay", { value:"shadow", handler:this.configUnderlay, supercedes:["visible"] } );

    	/**
    	* True if the Panel should be displayed in a modal fashion, automatically creating a transparent mask over the document that will not be removed until the Panel is dismissed.
    	* @config modal
    	* @type Boolean
    	* @default false
    	*/
    	this.cfg.addProperty("modal",	{ value:false, handler:this.configModal, validator:this.cfg.checkBoolean, supercedes:["visible"] } );

    	/**
    	* A KeyListener (or array of KeyListeners) that will be enabled when the Panel is shown, and disabled when the Panel is hidden.
    	* @config keylisteners
    	* @type YAHOO.util.KeyListener[]
    	* @default null
    	*/
    	this.cfg.addProperty("keylisteners", { handler:this.configKeyListeners, suppressEvent:true, supercedes:["visible"] } );
    };

    // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //

    /**
    * The default event handler fired when the "close" property is changed. The method controls the appending or hiding of the close icon at the top right of the Panel.
    * @method configClose
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configClose = function(type, args, obj) {
    	var val = args[0];

    	var doHide = function(e, obj) {
    		obj.hide();
    	};

    	if (val) {
    		if (! this.close) {
    			this.close = document.createElement("DIV");
    			YAHOO.util.Dom.addClass(this.close, "close");

    			if (this.isSecure) {
    				YAHOO.util.Dom.addClass(this.close, "secure");
    			} else {
    				YAHOO.util.Dom.addClass(this.close, "nonsecure");
    			}

    			this.close.innerHTML = "&nbsp;";
    			this.innerElement.appendChild(this.close);
    			YAHOO.util.Event.addListener(this.close, "click", doHide, this);
    		} else {
    			this.close.style.display = "block";
    		}
    	} else {
    		if (this.close) {
    			this.close.style.display = "none";
    		}
    	}
    };

    /**
    * The default event handler fired when the "draggable" property is changed.
    * @method configDraggable
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configDraggable = function(type, args, obj) {
    	var val = args[0];
    	if (val) {
    		if (this.header) {
    			YAHOO.util.Dom.setStyle(this.header,"cursor","move");
    			this.registerDragDrop();
    		}
    	} else {
    		if (this.dd) {
    			this.dd.unreg();
    		}
    		if (this.header) {
    			YAHOO.util.Dom.setStyle(this.header,"cursor","auto");
    		}
    	}
    };

    /**
    * The default event handler fired when the "underlay" property is changed.
    * @method configUnderlay
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configUnderlay = function(type, args, obj) {
    	var val = args[0];

    	switch (val.toLowerCase()) {
    		case "shadow":
    			YAHOO.util.Dom.removeClass(this.element, "matte");
    			YAHOO.util.Dom.addClass(this.element, "shadow");

    			if (! this.underlay) { // create if not already in DOM
    				this.underlay = document.createElement("DIV");
    				this.underlay.className = "underlay";
    				this.underlay.innerHTML = "&nbsp;";
    				this.element.appendChild(this.underlay);
    			}

    			this.sizeUnderlay();
    			break;
    		case "matte":
    			YAHOO.util.Dom.removeClass(this.element, "shadow");
    			YAHOO.util.Dom.addClass(this.element, "matte");
    			break;
    		default:
    			YAHOO.util.Dom.removeClass(this.element, "shadow");
    			YAHOO.util.Dom.removeClass(this.element, "matte");
    			break;
    	}
    };

    /**
    * The default event handler fired when the "modal" property is changed. This handler subscribes or unsubscribes to the show and hide events to handle the display or hide of the modality mask.
    * @method configModal
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configModal = function(type, args, obj) {
    	var modal = args[0];

    	if (modal) {
    		this.buildMask();

    		if (! YAHOO.util.Config.alreadySubscribed( this.beforeShowEvent, this.showMask, this ) ) {
    			this.beforeShowEvent.subscribe(this.showMask, this, true);
    		}
    		if (! YAHOO.util.Config.alreadySubscribed( this.hideEvent, this.hideMask, this) ) {
    			this.hideEvent.subscribe(this.hideMask, this, true);
    		}
    		if (! YAHOO.util.Config.alreadySubscribed( YAHOO.widget.Overlay.windowResizeEvent, this.sizeMask, this ) ) {
    			YAHOO.widget.Overlay.windowResizeEvent.subscribe(this.sizeMask, this, true);
    		}
    		if (! YAHOO.util.Config.alreadySubscribed( this.destroyEvent, this.removeMask, this) ) {
    			this.destroyEvent.subscribe(this.removeMask, this, true);
    		}

    		this.cfg.refireEvent("zIndex");
    	} else {
    		this.beforeShowEvent.unsubscribe(this.showMask, this);
    		this.hideEvent.unsubscribe(this.hideMask, this);
    		YAHOO.widget.Overlay.windowResizeEvent.unsubscribe(this.sizeMask, this);
    		this.destroyEvent.unsubscribe(this.removeMask, this);
    	}
    };

    /**
    * Removes the modality mask.
    * @method removeMask
    */
    YAHOO.widget.Panel.prototype.removeMask = function() {
    	if (this.mask) {
    		if (this.mask.parentNode) {
    			this.mask.parentNode.removeChild(this.mask);
    		}
    		this.mask = null;
    	}
    };

    /**
    * The default event handler fired when the "keylisteners" property is changed.
    * @method configKeyListeners
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configKeyListeners = function(type, args, obj) {
    	var listeners = args[0];

    	if (listeners) {
    		if (listeners instanceof Array) {
    			for (var i=0;i<listeners.length;i++) {
    				var listener = listeners[i];

    				if (! YAHOO.util.Config.alreadySubscribed(this.showEvent, listener.enable, listener)) {
    					this.showEvent.subscribe(listener.enable, listener, true);
    				}
    				if (! YAHOO.util.Config.alreadySubscribed(this.hideEvent, listener.disable, listener)) {
    					this.hideEvent.subscribe(listener.disable, listener, true);
    					this.destroyEvent.subscribe(listener.disable, listener, true);
    				}
    			}
    		} else {
    			if (! YAHOO.util.Config.alreadySubscribed(this.showEvent, listeners.enable, listeners)) {
    				this.showEvent.subscribe(listeners.enable, listeners, true);
    			}
    			if (! YAHOO.util.Config.alreadySubscribed(this.hideEvent, listeners.disable, listeners)) {
    				this.hideEvent.subscribe(listeners.disable, listeners, true);
    				this.destroyEvent.subscribe(listeners.disable, listeners, true);
    			}
    		}
    	}
    };

    /**
    * The default event handler fired when the "height" property is changed.
    * @method configHeight
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configHeight = function(type, args, obj) {
    	var height = args[0];
    	var el = this.innerElement;
    	YAHOO.util.Dom.setStyle(el, "height", height);
    	this.cfg.refireEvent("underlay");
    	this.cfg.refireEvent("iframe");
    };

    /**
    * The default event handler fired when the "width" property is changed.
    * @method configWidth
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configWidth = function(type, args, obj) {
    	var width = args[0];
    	var el = this.innerElement;
    	YAHOO.util.Dom.setStyle(el, "width", width);
    	this.cfg.refireEvent("underlay");
    	this.cfg.refireEvent("iframe");
    };

    /**
    * The default event handler fired when the "zIndex" property is changed.
    * @method configzIndex
    * @param {String} type	The CustomEvent type (usually the property name)
    * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
    * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
    */
    YAHOO.widget.Panel.prototype.configzIndex = function(type, args, obj) {
    	YAHOO.widget.Panel.superclass.configzIndex.call(this, type, args, obj);

    	var maskZ = 0;
    	var currentZ = YAHOO.util.Dom.getStyle(this.element, "zIndex");

    	if (this.mask) {
    		if (! currentZ || isNaN(currentZ)) {
    			currentZ = 0;
    		}

    		if (currentZ === 0) {
    			this.cfg.setProperty("zIndex", 1);
    		} else {
    			maskZ = currentZ - 1;
    			YAHOO.util.Dom.setStyle(this.mask, "zIndex", maskZ);
    		}

    	}
    };

    // END BUILT-IN PROPERTY EVENT HANDLERS //

    /**
    * Builds the wrapping container around the Panel that is used for positioning the shadow and matte underlays. The container element is assigned to a  local instance variable called container, and the element is reinserted inside of it.
    * @method buildWrapper
    */
    YAHOO.widget.Panel.prototype.buildWrapper = function() {
    	var elementParent = this.element.parentNode;
    	var originalElement = this.element;

    	var wrapper = document.createElement("DIV");
    	wrapper.className = YAHOO.widget.Panel.CSS_PANEL_CONTAINER;
    	wrapper.id = originalElement.id + "_c";

    	if (elementParent) {
    		elementParent.insertBefore(wrapper, originalElement);
    	}

    	wrapper.appendChild(originalElement);

    	this.element = wrapper;
    	this.innerElement = originalElement;

    	YAHOO.util.Dom.setStyle(this.innerElement, "visibility", "inherit");
    };

    /**
    * Adjusts the size of the shadow based on the size of the element.
    * @method sizeUnderlay
    */
    YAHOO.widget.Panel.prototype.sizeUnderlay = function() {
    	if (this.underlay && this.browser != "gecko" && this.browser != "safari") {
    		this.underlay.style.width = this.innerElement.offsetWidth + "px";
    		this.underlay.style.height = this.innerElement.offsetHeight + "px";
    	}
    };

    /**
    * Event handler fired when the resize monitor element is resized.
    * @method onDomResize
    * @param {DOMEvent} e	The resize DOM event
    * @param {Object} obj	The scope object
    */
    YAHOO.widget.Panel.prototype.onDomResize = function(e, obj) {
    	YAHOO.widget.Panel.superclass.onDomResize.call(this, e, obj);
    	var me = this;
    	setTimeout(function() {
    		me.sizeUnderlay();
    	}, 0);
    };

    /**
    * Registers the Panel's header for drag & drop capability.
    * @method registerDragDrop
    */
    YAHOO.widget.Panel.prototype.registerDragDrop = function() {
    	if (this.header) {
    		this.dd = new YAHOO.util.DD(this.element.id, this.id);

    		if (! this.header.id) {
    			this.header.id = this.id + "_h";
    		}

    		var me = this;

    		this.dd.startDrag = function() {

    			if (me.browser == "ie") {
    				YAHOO.util.Dom.addClass(me.element,"drag");
    			}

    			if (me.cfg.getProperty("constraintoviewport")) {
    				var offsetHeight = me.element.offsetHeight;
    				var offsetWidth = me.element.offsetWidth;

    				var viewPortWidth = YAHOO.util.Dom.getViewportWidth();
    				var viewPortHeight = YAHOO.util.Dom.getViewportHeight();

    				var scrollX = window.scrollX || document.documentElement.scrollLeft;
    				var scrollY = window.scrollY || document.documentElement.scrollTop;

    				var topConstraint = scrollY + 10;
    				var leftConstraint = scrollX + 10;
    				var bottomConstraint = scrollY + viewPortHeight - offsetHeight - 10;
    				var rightConstraint = scrollX + viewPortWidth - offsetWidth - 10;

    				this.minX = leftConstraint;
    				this.maxX = rightConstraint;
    				this.constrainX = true;

    				this.minY = topConstraint;
    				this.maxY = bottomConstraint;
    				this.constrainY = true;
    			} else {
    				this.constrainX = false;
    				this.constrainY = false;
    			}

    			me.dragEvent.fire("startDrag", arguments);
    		};

    		this.dd.onDrag = function() {
    			me.syncPosition();
    			me.cfg.refireEvent("iframe");
    			if (this.platform == "mac" && this.browser == "gecko") {
    				this.showMacGeckoScrollbars();
    			}

    			me.dragEvent.fire("onDrag", arguments);
    		};

    		this.dd.endDrag = function() {
    			if (me.browser == "ie") {
    				YAHOO.util.Dom.removeClass(me.element,"drag");
    			}

    			me.dragEvent.fire("endDrag", arguments);
    		};

    		this.dd.setHandleElId(this.header.id);
    		this.dd.addInvalidHandleType("INPUT");
    		this.dd.addInvalidHandleType("SELECT");
    		this.dd.addInvalidHandleType("TEXTAREA");
    	}
    };

    /**
    * Builds the mask that is laid over the document when the Panel is configured to be modal.
    * @method buildMask
    */
    YAHOO.widget.Panel.prototype.buildMask = function() {
    	if (! this.mask) {
    		this.mask = document.createElement("DIV");
    		this.mask.id = this.id + "_mask";
    		this.mask.className = "mask";
    		this.mask.innerHTML = "&nbsp;";

    		var maskClick = function(e, obj) {
    			YAHOO.util.Event.stopEvent(e);
    		};

    		var firstChild = document.body.firstChild;
    		if (firstChild)	{
    			document.body.insertBefore(this.mask, document.body.firstChild);
    		} else {
    			document.body.appendChild(this.mask);
    		}
    	}
    };

    /**
    * Hides the modality mask.
    * @method hideMask
    */
    YAHOO.widget.Panel.prototype.hideMask = function() {
    	if (this.cfg.getProperty("modal") && this.mask) {
    		this.mask.style.display = "none";
    		this.hideMaskEvent.fire();
    		YAHOO.util.Dom.removeClass(document.body, "masked");
    	}
    };

    /**
    * Shows the modality mask.
    * @method showMask
    */
    YAHOO.widget.Panel.prototype.showMask = function() {
    	if (this.cfg.getProperty("modal") && this.mask) {
    		YAHOO.util.Dom.addClass(document.body, "masked");
    		this.sizeMask();
    		this.mask.style.display = "block";
    		this.showMaskEvent.fire();
    	}
    };

    /**
    * Sets the size of the modality mask to cover the entire scrollable area of the document
    * @method sizeMask
    */
    YAHOO.widget.Panel.prototype.sizeMask = function() {
    	if (this.mask) {
    		this.mask.style.height = YAHOO.util.Dom.getDocumentHeight()+"px";
    		this.mask.style.width = YAHOO.util.Dom.getDocumentWidth()+"px";
    	}
    };

    /**
    * Renders the Panel by inserting the elements that are not already in the main Panel into their correct places. Optionally appends the Panel to the specified node prior to the render's execution. NOTE: For Panels without existing markup, the appendToNode argument is REQUIRED. If this argument is ommitted and the current element is not present in the document, the function will return false, indicating that the render was a failure.
    * @method render
    * @param {String}	appendToNode	The element id to which the Module should be appended to prior to rendering <em>OR</em>
    * @param {HTMLElement}	appendToNode	The element to which the Module should be appended to prior to rendering
    * @return {boolean} Success or failure of the render
    */
    YAHOO.widget.Panel.prototype.render = function(appendToNode) {
    	return YAHOO.widget.Panel.superclass.render.call(this, appendToNode, this.innerElement);
    };

    /**
    * Returns a String representation of the object.
    * @method toString
    * @return {String} The string representation of the Panel.
    */
    YAHOO.widget.Panel.prototype.toString = function() {
    	return "Panel " + this.id;
    };

Copyright © 2006 Yahoo! Inc. All rights reserved.