(function () {
/**
* 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);
};
var _currentModal = null;
var Lang = YAHOO.lang,
Util = YAHOO.util,
Dom = Util.Dom,
Event = Util.Event,
CustomEvent = Util.CustomEvent,
KeyListener = YAHOO.util.KeyListener,
Config = Util.Config,
Overlay = YAHOO.widget.Overlay,
Panel = YAHOO.widget.Panel,
UA = YAHOO.env.ua,
bIEQuirks = (UA.ie && (UA.ie <= 6 || document.compatMode == "BackCompat")),
m_oMaskTemplate,
m_oUnderlayTemplate,
m_oCloseIconTemplate,
/**
* Constant representing the name of the Panel's events
* @property EVENT_TYPES
* @private
* @final
* @type Object
*/
EVENT_TYPES = {
"SHOW_MASK": "showMask",
"HIDE_MASK": "hideMask",
"DRAG": "drag"
},
/**
* Constant representing the Panel's configuration properties
* @property DEFAULT_CONFIG
* @private
* @final
* @type Object
*/
DEFAULT_CONFIG = {
"CLOSE": {
key: "close",
value: true,
validator: Lang.isBoolean,
supercedes: ["visible"]
},
"DRAGGABLE": {
key: "draggable",
value: (Util.DD ? true : false),
validator: Lang.isBoolean,
supercedes: ["visible"]
},
"DRAG_ONLY" : {
key: "dragonly",
value: false,
validator: Lang.isBoolean,
supercedes: ["draggable"]
},
"UNDERLAY": {
key: "underlay",
value: "shadow",
supercedes: ["visible"]
},
"MODAL": {
key: "modal",
value: false,
validator: Lang.isBoolean,
supercedes: ["visible", "zindex"]
},
"KEY_LISTENERS": {
key: "keylisteners",
suppressEvent: true,
supercedes: ["visible"]
},
"STRINGS" : {
key: "strings",
supercedes: ["close"],
validator: Lang.isObject,
value: {
close: "Close"
}
}
};
/**
* Constant representing the default CSS class used for a Panel
* @property YAHOO.widget.Panel.CSS_PANEL
* @static
* @final
* @type String
*/
Panel.CSS_PANEL = "yui-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
*/
Panel.CSS_PANEL_CONTAINER = "yui-panel-container";
/**
* Constant representing the default set of focusable elements
* on the pagewhich Modal Panels will prevent access to, when
* the modal mask is displayed
*
* @property YAHOO.widget.Panel.FOCUSABLE
* @static
* @type Array
*/
Panel.FOCUSABLE = [
"a",
"button",
"select",
"textarea",
"input",
"iframe"
];
// Private CustomEvent listeners
/*
"beforeRender" event handler that creates an empty header for a Panel
instance if its "draggable" configuration property is set to "true"
and no header has been created.
*/
function createHeader(p_sType, p_aArgs) {
if (!this.header && this.cfg.getProperty("draggable")) {
this.setHeader(" ");
}
}
/*
"hide" event handler that sets a Panel instance's "width"
configuration property back to its original value before
"setWidthToOffsetWidth" was called.
*/
function restoreOriginalWidth(p_sType, p_aArgs, p_oObject) {
var sOriginalWidth = p_oObject[0],
sNewWidth = p_oObject[1],
oConfig = this.cfg,
sCurrentWidth = oConfig.getProperty("width");
if (sCurrentWidth == sNewWidth) {
oConfig.setProperty("width", sOriginalWidth);
}
this.unsubscribe("hide", restoreOriginalWidth, p_oObject);
}
/*
"beforeShow" event handler that sets a Panel instance's "width"
configuration property to the value of its root HTML
elements's offsetWidth
*/
function setWidthToOffsetWidth(p_sType, p_aArgs) {
var oConfig,
sOriginalWidth,
sNewWidth;
if (bIEQuirks) {
oConfig = this.cfg;
sOriginalWidth = oConfig.getProperty("width");
if (!sOriginalWidth || sOriginalWidth == "auto") {
sNewWidth = (this.element.offsetWidth + "px");
oConfig.setProperty("width", sNewWidth);
this.subscribe("hide", restoreOriginalWidth,
[(sOriginalWidth || ""), sNewWidth]);
}
}
}
YAHOO.extend(Panel, Overlay, {
/**
* 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.
*/
init: function (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
*/
Panel.superclass.init.call(this, el/*, userConfig*/);
this.beforeInitEvent.fire(Panel);
Dom.addClass(this.element, Panel.CSS_PANEL);
this.buildWrapper();
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
this.subscribe("showMask", this._addFocusHandlers);
this.subscribe("hideMask", this._removeFocusHandlers);
this.subscribe("beforeRender", createHeader);
this.subscribe("render", function() {
this.setFirstLastFocusable();
this.subscribe("changeContent", this.setFirstLastFocusable);
});
this.subscribe("show", this.focusFirst);
this.initEvent.fire(Panel);
},
/**
* @method _onElementFocus
* @private
*
* "focus" event handler for a focuable element. Used to automatically
* blur the element when it receives focus to ensure that a Panel
* instance's modality is not compromised.
*
* @param {Event} e The DOM event object
*/
_onElementFocus : function(e){
if(_currentModal === this) {
var target = Event.getTarget(e),
doc = document.documentElement,
insideDoc = (target !== doc && target !== window);
// mask and documentElement checks added for IE, which focuses on the mask when it's clicked on, and focuses on
// the documentElement, when the document scrollbars are clicked on
if (insideDoc && target !== this.element && target !== this.mask && !Dom.isAncestor(this.element, target)) {
try {
if (this.firstElement) {
this.firstElement.focus();
} else {
if (this._modalFocus) {
this._modalFocus.focus();
} else {
this.innerElement.focus();
}
}
} catch(err){
// Just in case we fail to focus
try {
if (insideDoc && target !== document.body) {
target.blur();
}
} catch(err2) { }
}
}
}
},
/**
* @method _addFocusHandlers
* @protected
*
* "showMask" event handler that adds a "focus" event handler to all
* focusable elements in the document to enforce a Panel instance's
* modality from being compromised.
*
* @param p_sType {String} Custom event type
* @param p_aArgs {Array} Custom event arguments
*/
_addFocusHandlers: function(p_sType, p_aArgs) {
if (!this.firstElement) {
if (UA.webkit || UA.opera) {
if (!this._modalFocus) {
this._createHiddenFocusElement();
}
} else {
this.innerElement.tabIndex = 0;
}
}
this.setTabLoop(this.firstElement, this.lastElement);
Event.onFocus(document.documentElement, this._onElementFocus, this, true);
_currentModal = this;
},
/**
* Creates a hidden focusable element, used to focus on,
* to enforce modality for browsers in which focus cannot
* be applied to the container box.
*
* @method _createHiddenFocusElement
* @private
*/
_createHiddenFocusElement : function() {
var e = document.createElement("button");
e.style.height = "1px";
e.style.width = "1px";
e.style.position = "absolute";
e.style.left = "-10000em";
e.style.opacity = 0;
e.tabIndex = -1;
this.innerElement.appendChild(e);
this._modalFocus = e;
},
/**
* @method _removeFocusHandlers
* @protected
*
* "hideMask" event handler that removes all "focus" event handlers added
* by the "addFocusEventHandlers" method.
*
* @param p_sType {String} Event type
* @param p_aArgs {Array} Event Arguments
*/
_removeFocusHandlers: function(p_sType, p_aArgs) {
Event.removeFocusListener(document.documentElement, this._onElementFocus, this);
if (_currentModal == this) {
_currentModal = null;
}
},
/**
* Sets focus to the first element in the Panel.
*
* @method focusFirst
*/
focusFirst: function (type, args, obj) {
var el = this.firstElement;
if (args && args[1]) {
Event.stopEvent(args[1]);
}
if (el) {
try {
el.focus();
} catch(err) {
// Ignore
}
}
},
/**
* Sets focus to the last element in the Panel.
*
* @method focusLast
*/
focusLast: function (type, args, obj) {
var el = this.lastElement;
if (args && args[1]) {
Event.stopEvent(args[1]);
}
if (el) {
try {
el.focus();
} catch(err) {
// Ignore
}
}
},
/**
* Sets up a tab, shift-tab loop between the first and last elements
* provided. NOTE: Sets up the preventBackTab and preventTabOut KeyListener
* instance properties, which are reset everytime this method is invoked.
*
* @method setTabLoop
* @param {HTMLElement} firstElement
* @param {HTMLElement} lastElement
*
*/
setTabLoop : function(firstElement, lastElement) {
var backTab = this.preventBackTab, tab = this.preventTabOut,
showEvent = this.showEvent, hideEvent = this.hideEvent;
if (backTab) {
backTab.disable();
showEvent.unsubscribe(backTab.enable, backTab);
hideEvent.unsubscribe(backTab.disable, backTab);
backTab = this.preventBackTab = null;
}
if (tab) {
tab.disable();
showEvent.unsubscribe(tab.enable, tab);
hideEvent.unsubscribe(tab.disable,tab);
tab = this.preventTabOut = null;
}
if (firstElement) {
this.preventBackTab = new KeyListener(firstElement,
{shift:true, keys:9},
{fn:this.focusLast, scope:this, correctScope:true}
);
backTab = this.preventBackTab;
showEvent.subscribe(backTab.enable, backTab, true);
hideEvent.subscribe(backTab.disable,backTab, true);
}
if (lastElement) {
this.preventTabOut = new KeyListener(lastElement,
{shift:false, keys:9},
{fn:this.focusFirst, scope:this, correctScope:true}
);
tab = this.preventTabOut;
showEvent.subscribe(tab.enable, tab, true);
hideEvent.subscribe(tab.disable,tab, true);
}
},
/**
* Returns an array of the currently focusable items which reside within
* Panel. The set of focusable elements the method looks for are defined
* in the Panel.FOCUSABLE static property
*
* @method getFocusableElements
* @param {HTMLElement} root element to start from.
*/
getFocusableElements : function(root) {
root = root || this.innerElement;
var focusable = {};
for (var i = 0; i < Panel.FOCUSABLE.length; i++) {
focusable[Panel.FOCUSABLE[i]] = true;
}
function isFocusable(el) {
if (el.focus && el.type !== "hidden" && !el.disabled && focusable[el.tagName.toLowerCase()]) {
return true;
}
return false;
}
// Not looking by Tag, since we want elements in DOM order
return Dom.getElementsBy(isFocusable, null, root);
},
/**
* Sets the firstElement and lastElement instance properties
* to the first and last focusable elements in the Panel.
*
* @method setFirstLastFocusable
*/
setFirstLastFocusable : function() {
this.firstElement = null;
this.lastElement = null;
var elements = this.getFocusableElements();
this.focusableElements = elements;
if (elements.length > 0) {
this.firstElement = elements[0];
this.lastElement = elements[elements.length - 1];
}
if (this.cfg.getProperty("modal")) {
this.setTabLoop(this.firstElement, this.lastElement);
}
},
/**
* Initializes the custom events for Module which are fired
* automatically at appropriate times by the Module class.
*/
initEvents: function () {
Panel.superclass.initEvents.call(this);
var SIGNATURE = CustomEvent.LIST;
/**
* CustomEvent fired after the modality mask is shown
* @event showMaskEvent
*/
this.showMaskEvent = this.createEvent(EVENT_TYPES.SHOW_MASK);
this.showMaskEvent.signature = SIGNATURE;
/**
* CustomEvent fired after the modality mask is hidden
* @event hideMaskEvent
*/
this.hideMaskEvent = this.createEvent(EVENT_TYPES.HIDE_MASK);
this.hideMaskEvent.signature = SIGNATURE;
/**
* CustomEvent when the Panel is dragged
* @event dragEvent
*/
this.dragEvent = this.createEvent(EVENT_TYPES.DRAG);
this.dragEvent.signature = SIGNATURE;
},
/**
* Initializes the class's configurable properties which can be changed
* using the Panel's Config object (cfg).
* @method initDefaultConfig
*/
initDefaultConfig: function () {
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(DEFAULT_CONFIG.CLOSE.key, {
handler: this.configClose,
value: DEFAULT_CONFIG.CLOSE.value,
validator: DEFAULT_CONFIG.CLOSE.validator,
supercedes: DEFAULT_CONFIG.CLOSE.supercedes
});
/**
* Boolean specifying if the Panel should be draggable. The default
* value is "true" if the Drag and Drop utility is included,
* otherwise it is "false." <strong>PLEASE NOTE:</strong> There is a
* known issue in IE 6 (Strict Mode and Quirks Mode) and IE 7
* (Quirks Mode) where Panels that either don't have a value set for
* their "width" configuration property, or their "width"
* configuration property is set to "auto" will only be draggable by
* placing the mouse on the text of the Panel's header element.
* To fix this bug, draggable Panels missing a value for their
* "width" configuration property, or whose "width" configuration
* property is set to "auto" will have it set to the value of
* their root HTML element's offsetWidth before they are made
* visible. The calculated width is then removed when the Panel is
* hidden. <em>This fix is only applied to draggable Panels in IE 6
* (Strict Mode and Quirks Mode) and IE 7 (Quirks Mode)</em>. For
* more information on this issue see:
* YUILibrary bugs #1726972 and #1589210.
* @config draggable
* @type Boolean
* @default true
*/
this.cfg.addProperty(DEFAULT_CONFIG.DRAGGABLE.key, {
handler: this.configDraggable,
value: (Util.DD) ? true : false,
validator: DEFAULT_CONFIG.DRAGGABLE.validator,
supercedes: DEFAULT_CONFIG.DRAGGABLE.supercedes
});
/**
* Boolean specifying if the draggable Panel should be drag only, not interacting with drop
* targets on the page.
* <p>
* When set to true, draggable Panels will not check to see if they are over drop targets,
* or fire the DragDrop events required to support drop target interaction (onDragEnter,
* onDragOver, onDragOut, onDragDrop etc.).
* If the Panel is not designed to be dropped on any target elements on the page, then this
* flag can be set to true to improve performance.
* </p>
* <p>
* When set to false, all drop target related events will be fired.
* </p>
* <p>
* The property is set to false by default to maintain backwards compatibility but should be
* set to true if drop target interaction is not required for the Panel, to improve performance.</p>
*
* @config dragOnly
* @type Boolean
* @default false
*/
this.cfg.addProperty(DEFAULT_CONFIG.DRAG_ONLY.key, {
value: DEFAULT_CONFIG.DRAG_ONLY.value,
validator: DEFAULT_CONFIG.DRAG_ONLY.validator,
supercedes: DEFAULT_CONFIG.DRAG_ONLY.supercedes
});
/**
* Sets the type of underlay to display for the Panel. Valid values
* are "shadow," "matte," and "none". <strong>PLEASE NOTE:</strong>
* The creation of the underlay element is deferred until the Panel
* is initially made visible. For Gecko-based browsers on Mac
* OS X the underlay elment is always created as it is used as a
* shim to prevent Aqua scrollbars below a Panel instance from poking
* through it (See YUILibrary bug #1723530).
* @config underlay
* @type String
* @default shadow
*/
this.cfg.addProperty(DEFAULT_CONFIG.UNDERLAY.key, {
handler: this.configUnderlay,
value: DEFAULT_CONFIG.UNDERLAY.value,
supercedes: DEFAULT_CONFIG.UNDERLAY.supercedes
});
/**
* 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(DEFAULT_CONFIG.MODAL.key, {
handler: this.configModal,
value: DEFAULT_CONFIG.MODAL.value,
validator: DEFAULT_CONFIG.MODAL.validator,
supercedes: DEFAULT_CONFIG.MODAL.supercedes
});
/**
* 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(DEFAULT_CONFIG.KEY_LISTENERS.key, {
handler: this.configKeyListeners,
suppressEvent: DEFAULT_CONFIG.KEY_LISTENERS.suppressEvent,
supercedes: DEFAULT_CONFIG.KEY_LISTENERS.supercedes
});
/**
* UI Strings used by the Panel
*
* @config strings
* @type Object
* @default An object literal with the properties shown below:
* <dl>
* <dt>close</dt><dd><em>String</em> : The string to use for the close icon. Defaults to "Close".</dd>
* </dl>
*/
this.cfg.addProperty(DEFAULT_CONFIG.STRINGS.key, {
value:DEFAULT_CONFIG.STRINGS.value,
handler:this.configStrings,
validator:DEFAULT_CONFIG.STRINGS.validator,
supercedes:DEFAULT_CONFIG.STRINGS.supercedes
});
},
// 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.
*/
configClose: function (type, args, obj) {
var val = args[0],
oClose = this.close,
strings = this.cfg.getProperty("strings");
if (val) {
if (!oClose) {
if (!m_oCloseIconTemplate) {
m_oCloseIconTemplate = document.createElement("a");
m_oCloseIconTemplate.className = "container-close";
m_oCloseIconTemplate.href = "#";
}
oClose = m_oCloseIconTemplate.cloneNode(true);
this.innerElement.appendChild(oClose);
oClose.innerHTML = (strings && strings.close) ? strings.close : " ";
Event.on(oClose, "click", this._doClose, this, true);
this.close = oClose;
} else {
oClose.style.display = "block";
}
} else {
if (oClose) {
oClose.style.display = "none";
}
}
},
/**
* Event handler for the close icon
*
* @method _doClose
* @protected
*
* @param {DOMEvent} e
*/
_doClose : function (e) {
Event.preventDefault(e);
this.hide();
},
/**
* 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.
*/
configDraggable: function (type, args, obj) {
var val = args[0];
if (val) {
if (!Util.DD) {
YAHOO.log("DD dependency not met.", "error");
this.cfg.setProperty("draggable", false);
return;
}
if (this.header) {
Dom.setStyle(this.header, "cursor", "move");
this.registerDragDrop();
}
this.subscribe("beforeShow", setWidthToOffsetWidth);
} else {
if (this.dd) {
this.dd.unreg();
}
if (this.header) {
Dom.setStyle(this.header,"cursor","auto");
}
this.unsubscribe("beforeShow", setWidthToOffsetWidth);
}
},
/**
* 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.
*/
configUnderlay: function (type, args, obj) {
var bMacGecko = (this.platform == "mac" && UA.gecko),
sUnderlay = args[0].toLowerCase(),
oUnderlay = this.underlay,
oElement = this.element;
function createUnderlay() {
var bNew = false;
if (!oUnderlay) { // create if not already in DOM
if (!m_oUnderlayTemplate) {
m_oUnderlayTemplate = document.createElement("div");
m_oUnderlayTemplate.className = "underlay";
}
oUnderlay = m_oUnderlayTemplate.cloneNode(false);
this.element.appendChild(oUnderlay);
this.underlay = oUnderlay;
if (bIEQuirks) {
this.sizeUnderlay();
this.cfg.subscribeToConfigEvent("width", this.sizeUnderlay);
this.cfg.subscribeToConfigEvent("height", this.sizeUnderlay);
this.changeContentEvent.subscribe(this.sizeUnderlay);
YAHOO.widget.Module.textResizeEvent.subscribe(this.sizeUnderlay, this, true);
}
if (UA.webkit && UA.webkit < 420) {
this.changeContentEvent.subscribe(this.forceUnderlayRedraw);
}
bNew = true;
}
}
function onBeforeShow() {
var bNew = createUnderlay.call(this);
if (!bNew && bIEQuirks) {
this.sizeUnderlay();
}
this._underlayDeferred = false;
this.beforeShowEvent.unsubscribe(onBeforeShow);
}
function destroyUnderlay() {
if (this._underlayDeferred) {
this.beforeShowEvent.unsubscribe(onBeforeShow);
this._underlayDeferred = false;
}
if (oUnderlay) {
this.cfg.unsubscribeFromConfigEvent("width", this.sizeUnderlay);
this.cfg.unsubscribeFromConfigEvent("height",this.sizeUnderlay);
this.changeContentEvent.unsubscribe(this.sizeUnderlay);
this.changeContentEvent.unsubscribe(this.forceUnderlayRedraw);
YAHOO.widget.Module.textResizeEvent.unsubscribe(this.sizeUnderlay, this, true);
this.element.removeChild(oUnderlay);
this.underlay = null;
}
}
switch (sUnderlay) {
case "shadow":
Dom.removeClass(oElement, "matte");
Dom.addClass(oElement, "shadow");
break;
case "matte":
if (!bMacGecko) {
destroyUnderlay.call(this);
}
Dom.removeClass(oElement, "shadow");
Dom.addClass(oElement, "matte");
break;
default:
if (!bMacGecko) {
destroyUnderlay.call(this);
}
Dom.removeClass(oElement, "shadow");
Dom.removeClass(oElement, "matte");
break;
}
if ((sUnderlay == "shadow") || (bMacGecko && !oUnderlay)) {
if (this.cfg.getProperty("visible")) {
var bNew = createUnderlay.call(this);
if (!bNew && bIEQuirks) {
this.sizeUnderlay();
}
} else {
if (!this._underlayDeferred) {
this.beforeShowEvent.subscribe(onBeforeShow);
this._underlayDeferred = true;
}
}
}
},
/**
* 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.
*/
configModal: function (type, args, obj) {
var modal = args[0];
if (modal) {
if (!this._hasModalityEventListeners) {
this.subscribe("beforeShow", this.buildMask);
this.subscribe("beforeShow", this.bringToTop);
this.subscribe("beforeShow", this.showMask);
this.subscribe("hide", this.hideMask);
Overlay.windowResizeEvent.subscribe(this.sizeMask,
this, true);
this._hasModalityEventListeners = true;
}
} else {
if (this._hasModalityEventListeners) {
if (this.cfg.getProperty("visible")) {
this.hideMask();
this.removeMask();
}
this.unsubscribe("beforeShow", this.buildMask);
this.unsubscribe("beforeShow", this.bringToTop);
this.unsubscribe("beforeShow", this.showMask);
this.unsubscribe("hide", this.hideMask);
Overlay.windowResizeEvent.unsubscribe(this.sizeMask, this);
this._hasModalityEventListeners = false;
}
}
},
/**
* Removes the modality mask.
* @method removeMask
*/
removeMask: function () {
var oMask = this.mask,
oParentNode;
if (oMask) {
/*
Hide the mask before destroying it to ensure that DOM
event handlers on focusable elements get removed.
*/
this.hideMask();
oParentNode = oMask.parentNode;
if (oParentNode) {
oParentNode.removeChild(oMask);
}
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.
*/
configKeyListeners: function (type, args, obj) {
var listeners = args[0],
listener,
nListeners,
i;
if (listeners) {
if (listeners instanceof Array) {
nListeners = listeners.length;
for (i = 0; i < nListeners; i++) {
listener = listeners[i];
if (!Config.alreadySubscribed(this.showEvent,
listener.enable, listener)) {
this.showEvent.subscribe(listener.enable,
listener, true);
}
if (!Config.alreadySubscribed(this.hideEvent,
listener.disable, listener)) {
this.hideEvent.subscribe(listener.disable,
listener, true);
this.destroyEvent.subscribe(listener.disable,
listener, true);
}
}
} else {
if (!Config.alreadySubscribed(this.showEvent,
listeners.enable, listeners)) {
this.showEvent.subscribe(listeners.enable,
listeners, true);
}
if (!Config.alreadySubscribed(this.hideEvent,
listeners.disable, listeners)) {
this.hideEvent.subscribe(listeners.disable,
listeners, true);
this.destroyEvent.subscribe(listeners.disable,
listeners, true);
}
}
}
},
/**
* The default handler for the "strings" property
* @method configStrings
*/
configStrings : function(type, args, obj) {
var val = Lang.merge(DEFAULT_CONFIG.STRINGS.value, args[0]);
this.cfg.setProperty(DEFAULT_CONFIG.STRINGS.key, val, 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.
*/
configHeight: function (type, args, obj) {
var height = args[0],
el = this.innerElement;
Dom.setStyle(el, "height", height);
this.cfg.refireEvent("iframe");
},
/**
* The default custom event handler executed when the Panel's height is changed,
* if the autofillheight property has been set.
*
* @method _autoFillOnHeightChange
* @protected
* @param {String} type The event type
* @param {Array} args The array of arguments passed to event subscribers
* @param {HTMLElement} el The header, body or footer element which is to be resized to fill
* out the containers height
*/
_autoFillOnHeightChange : function(type, args, el) {
Panel.superclass._autoFillOnHeightChange.apply(this, arguments);
if (bIEQuirks) {
var panel = this;
setTimeout(function() {
panel.sizeUnderlay();
},0);
}
},
/**
* 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.
*/
configWidth: function (type, args, obj) {
var width = args[0],
el = this.innerElement;
Dom.setStyle(el, "width", width);
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.
*/
configzIndex: function (type, args, obj) {
Panel.superclass.configzIndex.call(this, type, args, obj);
if (this.mask || this.cfg.getProperty("modal") === true) {
var panelZ = Dom.getStyle(this.element, "zIndex");
if (!panelZ || isNaN(panelZ)) {
panelZ = 0;
}
if (panelZ === 0) {
// Recursive call to configzindex (which should be stopped
// from going further because panelZ should no longer === 0)
this.cfg.setProperty("zIndex", 1);
} else {
this.stackMask();
}
}
},
// 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
*/
buildWrapper: function () {
var elementParent = this.element.parentNode,
originalElement = this.element,
wrapper = document.createElement("div");
wrapper.className = Panel.CSS_PANEL_CONTAINER;
wrapper.id = originalElement.id + "_c";
if (elementParent) {
elementParent.insertBefore(wrapper, originalElement);
}
wrapper.appendChild(originalElement);
this.element = wrapper;
this.innerElement = originalElement;
Dom.setStyle(this.innerElement, "visibility", "inherit");
},
/**
* Adjusts the size of the shadow based on the size of the element.
* @method sizeUnderlay
*/
sizeUnderlay: function () {
var oUnderlay = this.underlay,
oElement;
if (oUnderlay) {
oElement = this.element;
oUnderlay.style.width = oElement.offsetWidth + "px";
oUnderlay.style.height = oElement.offsetHeight + "px";
}
},
/**
* Registers the Panel's header for drag & drop capability.
* @method registerDragDrop
*/
registerDragDrop: function () {
var me = this;
if (this.header) {
if (!Util.DD) {
YAHOO.log("DD dependency not met.", "error");
return;
}
var bDragOnly = (this.cfg.getProperty("dragonly") === true);
/**
* The YAHOO.util.DD instance, used to implement the draggable header for the panel if draggable is enabled
*
* @property dd
* @type YAHOO.util.DD
*/
this.dd = new Util.DD(this.element.id, this.id, {dragOnly: bDragOnly});
if (!this.header.id) {
this.header.id = this.id + "_h";
}
this.dd.startDrag = function () {
var offsetHeight,
offsetWidth,
viewPortWidth,
viewPortHeight,
scrollX,
scrollY;
if (YAHOO.env.ua.ie == 6) {
Dom.addClass(me.element,"drag");
}
if (me.cfg.getProperty("constraintoviewport")) {
var nViewportOffset = Overlay.VIEWPORT_OFFSET;
offsetHeight = me.element.offsetHeight;
offsetWidth = me.element.offsetWidth;
viewPortWidth = Dom.getViewportWidth();
viewPortHeight = Dom.getViewportHeight();
scrollX = Dom.getDocumentScrollLeft();
scrollY = Dom.getDocumentScrollTop();
if (offsetHeight + nViewportOffset < viewPortHeight) {
this.minY = scrollY + nViewportOffset;
this.maxY = scrollY + viewPortHeight - offsetHeight - nViewportOffset;
} else {
this.minY = scrollY + nViewportOffset;
this.maxY = scrollY + nViewportOffset;
}
if (offsetWidth + nViewportOffset < viewPortWidth) {
this.minX = scrollX + nViewportOffset;
this.maxX = scrollX + viewPortWidth - offsetWidth - nViewportOffset;
} else {
this.minX = scrollX + nViewportOffset;
this.maxX = scrollX + nViewportOffset;
}
this.constrainX = true;
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" && YAHOO.env.ua.gecko) {
this.showMacGeckoScrollbars();
}
me.dragEvent.fire("onDrag", arguments);
};
this.dd.endDrag = function () {
if (YAHOO.env.ua.ie == 6) {
Dom.removeClass(me.element,"drag");
}
me.dragEvent.fire("endDrag", arguments);
me.moveEvent.fire(me.cfg.getProperty("xy"));
};
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
*/
buildMask: function () {
var oMask = this.mask;
if (!oMask) {
if (!m_oMaskTemplate) {
m_oMaskTemplate = document.createElement("div");
m_oMaskTemplate.className = "mask";
m_oMaskTemplate.innerHTML = " ";
}
oMask = m_oMaskTemplate.cloneNode(true);
oMask.id = this.id + "_mask";
document.body.insertBefore(oMask, document.body.firstChild);
this.mask = oMask;
if (YAHOO.env.ua.gecko && this.platform == "mac") {
Dom.addClass(this.mask, "block-scrollbars");
}
// Stack mask based on the element zindex
this.stackMask();
}
},
/**
* Hides the modality mask.
* @method hideMask
*/
hideMask: function () {
if (this.cfg.getProperty("modal") && this.mask) {
this.mask.style.display = "none";
Dom.removeClass(document.body, "masked");
this.hideMaskEvent.fire();
}
},
/**
* Shows the modality mask.
* @method showMask
*/
showMask: function () {
if (this.cfg.getProperty("modal") && this.mask) {
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
*/
sizeMask: function () {
if (this.mask) {
// Shrink mask first, so it doesn't affect the document size.
var mask = this.mask,
viewWidth = Dom.getViewportWidth(),
viewHeight = Dom.getViewportHeight();
if (mask.offsetHeight > viewHeight) {
mask.style.height = viewHeight + "px";
}
if (mask.offsetWidth > viewWidth) {
mask.style.width = viewWidth + "px";
}
// Then size it to the document
mask.style.height = Dom.getDocumentHeight() + "px";
mask.style.width = Dom.getDocumentWidth() + "px";
}
},
/**
* Sets the zindex of the mask, if it exists, based on the zindex of
* the Panel element. The zindex of the mask is set to be one less
* than the Panel element's zindex.
*
* <p>NOTE: This method will not bump up the zindex of the Panel
* to ensure that the mask has a non-negative zindex. If you require the
* mask zindex to be 0 or higher, the zindex of the Panel
* should be set to a value higher than 0, before this method is called.
* </p>
* @method stackMask
*/
stackMask: function() {
if (this.mask) {
var panelZ = Dom.getStyle(this.element, "zIndex");
if (!YAHOO.lang.isUndefined(panelZ) && !isNaN(panelZ)) {
Dom.setStyle(this.mask, "zIndex", panelZ - 1);
}
}
},
/**
* 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
*/
render: function (appendToNode) {
return Panel.superclass.render.call(this, appendToNode, this.innerElement);
},
/**
* Renders the currently set header into it's proper position under the
* module element. If the module element is not provided, "this.innerElement"
* is used.
*
* @method _renderHeader
* @protected
* @param {HTMLElement} moduleElement Optional. A reference to the module element
*/
_renderHeader: function(moduleElement){
moduleElement = moduleElement || this.innerElement;
Panel.superclass._renderHeader.call(this, moduleElement);
},
/**
* Renders the currently set body into it's proper position under the
* module element. If the module element is not provided, "this.innerElement"
* is used.
*
* @method _renderBody
* @protected
* @param {HTMLElement} moduleElement Optional. A reference to the module element.
*/
_renderBody: function(moduleElement){
moduleElement = moduleElement || this.innerElement;
Panel.superclass._renderBody.call(this, moduleElement);
},
/**
* Renders the currently set footer into it's proper position under the
* module element. If the module element is not provided, "this.innerElement"
* is used.
*
* @method _renderFooter
* @protected
* @param {HTMLElement} moduleElement Optional. A reference to the module element
*/
_renderFooter: function(moduleElement){
moduleElement = moduleElement || this.innerElement;
Panel.superclass._renderFooter.call(this, moduleElement);
},
/**
* Removes the Panel element from the DOM and sets all child elements
* to null.
* @method destroy
*/
destroy: function () {
Overlay.windowResizeEvent.unsubscribe(this.sizeMask, this);
this.removeMask();
if (this.close) {
Event.purgeElement(this.close);
}
Panel.superclass.destroy.call(this);
},
/**
* Forces the underlay element to be repainted through the application/removal
* of a yui-force-redraw class to the underlay element.
*
* @method forceUnderlayRedraw
*/
forceUnderlayRedraw : function () {
var u = this.underlay;
Dom.addClass(u, "yui-force-redraw");
setTimeout(function(){Dom.removeClass(u, "yui-force-redraw");}, 0);
},
/**
* Returns a String representation of the object.
* @method toString
* @return {String} The string representation of the Panel.
*/
toString: function () {
return "Panel " + this.id;
}
});
}());