/*
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 2.8.1
*/
(function () {
/**
* Config is a utility used within an Object to allow the implementer to
* maintain a list of local configuration properties and listen for changes
* to those properties dynamically using CustomEvent. The initial values are
* also maintained so that the configuration can be reset at any given point
* to its initial state.
* @namespace YAHOO.util
* @class Config
* @constructor
* @param {Object} owner The owner Object to which this Config Object belongs
*/
YAHOO.util.Config = function (owner) {
if (owner) {
this.init(owner);
}
if (!owner) { YAHOO.log("No owner specified for Config object", "error", "Config"); }
};
var Lang = YAHOO.lang,
CustomEvent = YAHOO.util.CustomEvent,
Config = YAHOO.util.Config;
/**
* Constant representing the CustomEvent type for the config changed event.
* @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
* @private
* @static
* @final
*/
Config.CONFIG_CHANGED_EVENT = "configChanged";
/**
* Constant representing the boolean type string
* @property YAHOO.util.Config.BOOLEAN_TYPE
* @private
* @static
* @final
*/
Config.BOOLEAN_TYPE = "boolean";
Config.prototype = {
/**
* Object reference to the owner of this Config Object
* @property owner
* @type Object
*/
owner: null,
/**
* Boolean flag that specifies whether a queue is currently
* being executed
* @property queueInProgress
* @type Boolean
*/
queueInProgress: false,
/**
* Maintains the local collection of configuration property objects and
* their specified values
* @property config
* @private
* @type Object
*/
config: null,
/**
* Maintains the local collection of configuration property objects as
* they were initially applied.
* This object is used when resetting a property.
* @property initialConfig
* @private
* @type Object
*/
initialConfig: null,
/**
* Maintains the local, normalized CustomEvent queue
* @property eventQueue
* @private
* @type Object
*/
eventQueue: null,
/**
* Custom Event, notifying subscribers when Config properties are set
* (setProperty is called without the silent flag
* @event configChangedEvent
*/
configChangedEvent: null,
/**
* Initializes the configuration Object and all of its local members.
* @method init
* @param {Object} owner The owner Object to which this Config
* Object belongs
*/
init: function (owner) {
this.owner = owner;
this.configChangedEvent =
this.createEvent(Config.CONFIG_CHANGED_EVENT);
this.configChangedEvent.signature = CustomEvent.LIST;
this.queueInProgress = false;
this.config = {};
this.initialConfig = {};
this.eventQueue = [];
},
/**
* Validates that the value passed in is a Boolean.
* @method checkBoolean
* @param {Object} val The value to validate
* @return {Boolean} true, if the value is valid
*/
checkBoolean: function (val) {
return (typeof val == Config.BOOLEAN_TYPE);
},
/**
* Validates that the value passed in is a number.
* @method checkNumber
* @param {Object} val The value to validate
* @return {Boolean} true, if the value is valid
*/
checkNumber: function (val) {
return (!isNaN(val));
},
/**
* Fires a configuration property event using the specified value.
* @method fireEvent
* @private
* @param {String} key The configuration property's name
* @param {value} Object The value of the correct type for the property
*/
fireEvent: function ( key, value ) {
YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config");
var property = this.config[key];
if (property && property.event) {
property.event.fire(value);
}
},
/**
* Adds a property to the Config Object's private config hash.
* @method addProperty
* @param {String} key The configuration property's name
* @param {Object} propertyObject The Object containing all of this
* property's arguments
*/
addProperty: function ( key, propertyObject ) {
key = key.toLowerCase();
YAHOO.log("Added property: " + key, "info", "Config");
this.config[key] = propertyObject;
propertyObject.event = this.createEvent(key, { scope: this.owner });
propertyObject.event.signature = CustomEvent.LIST;
propertyObject.key = key;
if (propertyObject.handler) {
propertyObject.event.subscribe(propertyObject.handler,
this.owner);
}
this.setProperty(key, propertyObject.value, true);
if (! propertyObject.suppressEvent) {
this.queueProperty(key, propertyObject.value);
}
},
/**
* Returns a key-value configuration map of the values currently set in
* the Config Object.
* @method getConfig
* @return {Object} The current config, represented in a key-value map
*/
getConfig: function () {
var cfg = {},
currCfg = this.config,
prop,
property;
for (prop in currCfg) {
if (Lang.hasOwnProperty(currCfg, prop)) {
property = currCfg[prop];
if (property && property.event) {
cfg[prop] = property.value;
}
}
}
return cfg;
},
/**
* Returns the value of specified property.
* @method getProperty
* @param {String} key The name of the property
* @return {Object} The value of the specified property
*/
getProperty: function (key) {
var property = this.config[key.toLowerCase()];
if (property && property.event) {
return property.value;
} else {
return undefined;
}
},
/**
* Resets the specified property's value to its initial value.
* @method resetProperty
* @param {String} key The name of the property
* @return {Boolean} True is the property was reset, false if not
*/
resetProperty: function (key) {
key = key.toLowerCase();
var property = this.config[key];
if (property && property.event) {
if (this.initialConfig[key] &&
!Lang.isUndefined(this.initialConfig[key])) {
this.setProperty(key, this.initialConfig[key]);
return true;
}
} else {
return false;
}
},
/**
* Sets the value of a property. If the silent property is passed as
* true, the property's event will not be fired.
* @method setProperty
* @param {String} key The name of the property
* @param {String} value The value to set the property to
* @param {Boolean} silent Whether the value should be set silently,
* without firing the property event.
* @return {Boolean} True, if the set was successful, false if it failed.
*/
setProperty: function (key, value, silent) {
var property;
key = key.toLowerCase();
YAHOO.log("setProperty: " + key + "=" + value, "info", "Config");
if (this.queueInProgress && ! silent) {
// Currently running through a queue...
this.queueProperty(key,value);
return true;
} else {
property = this.config[key];
if (property && property.event) {
if (property.validator && !property.validator(value)) {
return false;
} else {
property.value = value;
if (! silent) {
this.fireEvent(key, value);
this.configChangedEvent.fire([key, value]);
}
return true;
}
} else {
return false;
}
}
},
/**
* Sets the value of a property and queues its event to execute. If the
* event is already scheduled to execute, it is
* moved from its current position to the end of the queue.
* @method queueProperty
* @param {String} key The name of the property
* @param {String} value The value to set the property to
* @return {Boolean} true, if the set was successful, false if
* it failed.
*/
queueProperty: function (key, value) {
key = key.toLowerCase();
YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config");
var property = this.config[key],
foundDuplicate = false,
iLen,
queueItem,
queueItemKey,
queueItemValue,
sLen,
supercedesCheck,
qLen,
queueItemCheck,
queueItemCheckKey,
queueItemCheckValue,
i,
s,
q;
if (property && property.event) {
if (!Lang.isUndefined(value) && property.validator &&
!property.validator(value)) { // validator
return false;
} else {
if (!Lang.isUndefined(value)) {
property.value = value;
} else {
value = property.value;
}
foundDuplicate = false;
iLen = this.eventQueue.length;
for (i = 0; i < iLen; i++) {
queueItem = this.eventQueue[i];
if (queueItem) {
queueItemKey = queueItem[0];
queueItemValue = queueItem[1];
if (queueItemKey == key) {
/*
found a dupe... push to end of queue, null
current item, and break
*/
this.eventQueue[i] = null;
this.eventQueue.push(
[key, (!Lang.isUndefined(value) ?
value : queueItemValue)]);
foundDuplicate = true;
break;
}
}
}
// this is a refire, or a new property in the queue
if (! foundDuplicate && !Lang.isUndefined(value)) {
this.eventQueue.push([key, value]);
}
}
if (property.supercedes) {
sLen = property.supercedes.length;
for (s = 0; s < sLen; s++) {
supercedesCheck = property.supercedes[s];
qLen = this.eventQueue.length;
for (q = 0; q < qLen; q++) {
queueItemCheck = this.eventQueue[q];
if (queueItemCheck) {
queueItemCheckKey = queueItemCheck[0];
queueItemCheckValue = queueItemCheck[1];
if (queueItemCheckKey ==
supercedesCheck.toLowerCase() ) {
this.eventQueue.push([queueItemCheckKey,
queueItemCheckValue]);
this.eventQueue[q] = null;
break;
}
}
}
}
}
YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config");
return true;
} else {
return false;
}
},
/**
* Fires the event for a property using the property's current value.
* @method refireEvent
* @param {String} key The name of the property
*/
refireEvent: function (key) {
key = key.toLowerCase();
var property = this.config[key];
if (property && property.event &&
!Lang.isUndefined(property.value)) {
if (this.queueInProgress) {
this.queueProperty(key);
} else {
this.fireEvent(key, property.value);
}
}
},
/**
* Applies a key-value Object literal to the configuration, replacing
* any existing values, and queueing the property events.
* Although the values will be set, fireQueue() must be called for their
* associated events to execute.
* @method applyConfig
* @param {Object} userConfig The configuration Object literal
* @param {Boolean} init When set to true, the initialConfig will
* be set to the userConfig passed in, so that calling a reset will
* reset the properties to the passed values.
*/
applyConfig: function (userConfig, init) {
var sKey,
oConfig;
if (init) {
oConfig = {};
for (sKey in userConfig) {
if (Lang.hasOwnProperty(userConfig, sKey)) {
oConfig[sKey.toLowerCase()] = userConfig[sKey];
}
}
this.initialConfig = oConfig;
}
for (sKey in userConfig) {
if (Lang.hasOwnProperty(userConfig, sKey)) {
this.queueProperty(sKey, userConfig[sKey]);
}
}
},
/**
* Refires the events for all configuration properties using their
* current values.
* @method refresh
*/
refresh: function () {
var prop;
for (prop in this.config) {
if (Lang.hasOwnProperty(this.config, prop)) {
this.refireEvent(prop);
}
}
},
/**
* Fires the normalized list of queued property change events
* @method fireQueue
*/
fireQueue: function () {
var i,
queueItem,
key,
value,
property;
this.queueInProgress = true;
for (i = 0;i < this.eventQueue.length; i++) {
queueItem = this.eventQueue[i];
if (queueItem) {
key = queueItem[0];
value = queueItem[1];
property = this.config[key];
property.value = value;
// Clear out queue entry, to avoid it being
// re-added to the queue by any queueProperty/supercedes
// calls which are invoked during fireEvent
this.eventQueue[i] = null;
this.fireEvent(key,value);
}
}
this.queueInProgress = false;
this.eventQueue = [];
},
/**
* Subscribes an external handler to the change event for any
* given property.
* @method subscribeToConfigEvent
* @param {String} key The property name
* @param {Function} handler The handler function to use subscribe to
* the property's event
* @param {Object} obj The Object to use for scoping the event handler
* (see CustomEvent documentation)
* @param {Boolean} overrideContext Optional. If true, will override
* "this" within the handler to map to the scope Object passed into the
* method.
* @return {Boolean} True, if the subscription was successful,
* otherwise false.
*/
subscribeToConfigEvent: function (key, handler, obj, overrideContext) {
var property = this.config[key.toLowerCase()];
if (property && property.event) {
if (!Config.alreadySubscribed(property.event, handler, obj)) {
property.event.subscribe(handler, obj, overrideContext);
}
return true;
} else {
return false;
}
},
/**
* Unsubscribes an external handler from the change event for any
* given property.
* @method unsubscribeFromConfigEvent
* @param {String} key The property name
* @param {Function} handler The handler function to use subscribe to
* the property's event
* @param {Object} obj The Object to use for scoping the event
* handler (see CustomEvent documentation)
* @return {Boolean} True, if the unsubscription was successful,
* otherwise false.
*/
unsubscribeFromConfigEvent: function (key, handler, obj) {
var property = this.config[key.toLowerCase()];
if (property && property.event) {
return property.event.unsubscribe(handler, obj);
} else {
return false;
}
},
/**
* Returns a string representation of the Config object
* @method toString
* @return {String} The Config object in string format.
*/
toString: function () {
var output = "Config";
if (this.owner) {
output += " [" + this.owner.toString() + "]";
}
return output;
},
/**
* Returns a string representation of the Config object's current
* CustomEvent queue
* @method outputEventQueue
* @return {String} The string list of CustomEvents currently queued
* for execution
*/
outputEventQueue: function () {
var output = "",
queueItem,
q,
nQueue = this.eventQueue.length;
for (q = 0; q < nQueue; q++) {
queueItem = this.eventQueue[q];
if (queueItem) {
output += queueItem[0] + "=" + queueItem[1] + ", ";
}
}
return output;
},
/**
* Sets all properties to null, unsubscribes all listeners from each
* property's change event and all listeners from the configChangedEvent.
* @method destroy
*/
destroy: function () {
var oConfig = this.config,
sProperty,
oProperty;
for (sProperty in oConfig) {
if (Lang.hasOwnProperty(oConfig, sProperty)) {
oProperty = oConfig[sProperty];
oProperty.event.unsubscribeAll();
oProperty.event = null;
}
}
this.configChangedEvent.unsubscribeAll();
this.configChangedEvent = null;
this.owner = null;
this.config = null;
this.initialConfig = null;
this.eventQueue = null;
}
};
/**
* Checks to determine if a particular function/Object pair are already
* subscribed to the specified CustomEvent
* @method YAHOO.util.Config.alreadySubscribed
* @static
* @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check
* the subscriptions
* @param {Function} fn The function to look for in the subscribers list
* @param {Object} obj The execution scope Object for the subscription
* @return {Boolean} true, if the function/Object pair is already subscribed
* to the CustomEvent passed in
*/
Config.alreadySubscribed = function (evt, fn, obj) {
var nSubscribers = evt.subscribers.length,
subsc,
i;
if (nSubscribers > 0) {
i = nSubscribers - 1;
do {
subsc = evt.subscribers[i];
if (subsc && subsc.obj == obj && subsc.fn == fn) {
return true;
}
}
while (i--);
}
return false;
};
YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);
}());
(function () {
/**
* The Container family of components is designed to enable developers to
* create different kinds of content-containing modules on the web. Module
* and Overlay are the most basic containers, and they can be used directly
* or extended to build custom containers. Also part of the Container family
* are four UI controls that extend Module and Overlay: Tooltip, Panel,
* Dialog, and SimpleDialog.
* @module container
* @title Container
* @requires yahoo, dom, event
* @optional dragdrop, animation, button
*/
/**
* Module is a JavaScript representation of the Standard Module Format.
* Standard Module Format is a simple standard for markup containers where
* child nodes representing the header, body, and footer of the content are
* denoted using the CSS classes "hd", "bd", and "ft" respectively.
* Module is the base class for all other classes in the YUI
* Container package.
* @namespace YAHOO.widget
* @class Module
* @constructor
* @param {String} el The element ID representing the Module <em>OR</em>
* @param {HTMLElement} el The element representing the Module
* @param {Object} userConfig The configuration Object literal containing
* the configuration that should be set for this module. See configuration
* documentation for more details.
*/
YAHOO.widget.Module = function (el, userConfig) {
if (el) {
this.init(el, userConfig);
} else {
YAHOO.log("No element or element ID specified" +
" for Module instantiation", "error");
}
};
var Dom = YAHOO.util.Dom,
Config = YAHOO.util.Config,
Event = YAHOO.util.Event,
CustomEvent = YAHOO.util.CustomEvent,
Module = YAHOO.widget.Module,
UA = YAHOO.env.ua,
m_oModuleTemplate,
m_oHeaderTemplate,
m_oBodyTemplate,
m_oFooterTemplate,
/**
* Constant representing the name of the Module's events
* @property EVENT_TYPES
* @private
* @final
* @type Object
*/
EVENT_TYPES = {
"BEFORE_INIT": "beforeInit",
"INIT": "init",
"APPEND": "append",
"BEFORE_RENDER": "beforeRender",
"RENDER": "render",
"CHANGE_HEADER": "changeHeader",
"CHANGE_BODY": "changeBody",
"CHANGE_FOOTER": "changeFooter",
"CHANGE_CONTENT": "changeContent",
"DESTROY": "destroy",
"BEFORE_SHOW": "beforeShow",
"SHOW": "show",
"BEFORE_HIDE": "beforeHide",
"HIDE": "hide"
},
/**
* Constant representing the Module's configuration properties
* @property DEFAULT_CONFIG
* @private
* @final
* @type Object
*/
DEFAULT_CONFIG = {
"VISIBLE": {
key: "visible",
value: true,
validator: YAHOO.lang.isBoolean
},
"EFFECT": {
key: "effect",
suppressEvent: true,
supercedes: ["visible"]
},
"MONITOR_RESIZE": {
key: "monitorresize",
value: true
},
"APPEND_TO_DOCUMENT_BODY": {
key: "appendtodocumentbody",
value: false
}
};
/**
* Constant representing the prefix path to use for non-secure images
* @property YAHOO.widget.Module.IMG_ROOT
* @static
* @final
* @type String
*/
Module.IMG_ROOT = null;
/**
* Constant representing the prefix path to use for securely served images
* @property YAHOO.widget.Module.IMG_ROOT_SSL
* @static
* @final
* @type String
*/
Module.IMG_ROOT_SSL = null;
/**
* Constant for the default CSS class name that represents a Module
* @property YAHOO.widget.Module.CSS_MODULE
* @static
* @final
* @type String
*/
Module.CSS_MODULE = "yui-module";
/**
* Constant representing the module header
* @property YAHOO.widget.Module.CSS_HEADER
* @static
* @final
* @type String
*/
Module.CSS_HEADER = "hd";
/**
* Constant representing the module body
* @property YAHOO.widget.Module.CSS_BODY
* @static
* @final
* @type String
*/
Module.CSS_BODY = "bd";
/**
* Constant representing the module footer
* @property YAHOO.widget.Module.CSS_FOOTER
* @static
* @final
* @type String
*/
Module.CSS_FOOTER = "ft";
/**
* Constant representing the url for the "src" attribute of the iframe
* used to monitor changes to the browser's base font size
* @property YAHOO.widget.Module.RESIZE_MONITOR_SECURE_URL
* @static
* @final
* @type String
*/
Module.RESIZE_MONITOR_SECURE_URL = "javascript:false;";
/**
* Constant representing the buffer amount (in pixels) to use when positioning
* the text resize monitor offscreen. The resize monitor is positioned
* offscreen by an amount eqaul to its offsetHeight + the buffer value.
*
* @property YAHOO.widget.Module.RESIZE_MONITOR_BUFFER
* @static
* @type Number
*/
// Set to 1, to work around pixel offset in IE8, which increases when zoom is used
Module.RESIZE_MONITOR_BUFFER = 1;
/**
* Singleton CustomEvent fired when the font size is changed in the browser.
* Opera's "zoom" functionality currently does not support text
* size detection.
* @event YAHOO.widget.Module.textResizeEvent
*/
Module.textResizeEvent = new CustomEvent("textResize");
/**
* Helper utility method, which forces a document level
* redraw for Opera, which can help remove repaint
* irregularities after applying DOM changes.
*
* @method YAHOO.widget.Module.forceDocumentRedraw
* @static
*/
Module.forceDocumentRedraw = function() {
var docEl = document.documentElement;
if (docEl) {
docEl.className += " ";
docEl.className = YAHOO.lang.trim(docEl.className);
}
};
function createModuleTemplate() {
if (!m_oModuleTemplate) {
m_oModuleTemplate = document.createElement("div");
m_oModuleTemplate.innerHTML = ("<div class=\"" +
Module.CSS_HEADER + "\"></div>" + "<div class=\"" +
Module.CSS_BODY + "\"></div><div class=\"" +
Module.CSS_FOOTER + "\"></div>");
m_oHeaderTemplate = m_oModuleTemplate.firstChild;
m_oBodyTemplate = m_oHeaderTemplate.nextSibling;
m_oFooterTemplate = m_oBodyTemplate.nextSibling;
}
return m_oModuleTemplate;
}
function createHeader() {
if (!m_oHeaderTemplate) {
createModuleTemplate();
}
return (m_oHeaderTemplate.cloneNode(false));
}
function createBody() {
if (!m_oBodyTemplate) {
createModuleTemplate();
}
return (m_oBodyTemplate.cloneNode(false));
}
function createFooter() {
if (!m_oFooterTemplate) {
createModuleTemplate();
}
return (m_oFooterTemplate.cloneNode(false));
}
Module.prototype = {
/**
* The class's constructor function
* @property contructor
* @type Function
*/
constructor: Module,
/**
* The main module element that contains the header, body, and footer
* @property element
* @type HTMLElement
*/
element: null,
/**
* The header element, denoted with CSS class "hd"
* @property header
* @type HTMLElement
*/
header: null,
/**
* The body element, denoted with CSS class "bd"
* @property body
* @type HTMLElement
*/
body: null,
/**
* The footer element, denoted with CSS class "ft"
* @property footer
* @type HTMLElement
*/
footer: null,
/**
* The id of the element
* @property id
* @type String
*/
id: null,
/**
* A string representing the root path for all images created by
* a Module instance.
* @deprecated It is recommend that any images for a Module be applied
* via CSS using the "background-image" property.
* @property imageRoot
* @type String
*/
imageRoot: Module.IMG_ROOT,
/**
* Initializes the custom events for Module which are fired
* automatically at appropriate times by the Module class.
* @method initEvents
*/
initEvents: function () {
var SIGNATURE = CustomEvent.LIST;
/**
* CustomEvent fired prior to class initalization.
* @event beforeInitEvent
* @param {class} classRef class reference of the initializing
* class, such as this.beforeInitEvent.fire(Module)
*/
this.beforeInitEvent = this.createEvent(EVENT_TYPES.BEFORE_INIT);
this.beforeInitEvent.signature = SIGNATURE;
/**
* CustomEvent fired after class initalization.
* @event initEvent
* @param {class} classRef class reference of the initializing
* class, such as this.beforeInitEvent.fire(Module)
*/
this.initEvent = this.createEvent(EVENT_TYPES.INIT);
this.initEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the Module is appended to the DOM
* @event appendEvent
*/
this.appendEvent = this.createEvent(EVENT_TYPES.APPEND);
this.appendEvent.signature = SIGNATURE;
/**
* CustomEvent fired before the Module is rendered
* @event beforeRenderEvent
*/
this.beforeRenderEvent = this.createEvent(EVENT_TYPES.BEFORE_RENDER);
this.beforeRenderEvent.signature = SIGNATURE;
/**
* CustomEvent fired after the Module is rendered
* @event renderEvent
*/
this.renderEvent = this.createEvent(EVENT_TYPES.RENDER);
this.renderEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the header content of the Module
* is modified
* @event changeHeaderEvent
* @param {String/HTMLElement} content String/element representing
* the new header content
*/
this.changeHeaderEvent = this.createEvent(EVENT_TYPES.CHANGE_HEADER);
this.changeHeaderEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the body content of the Module is modified
* @event changeBodyEvent
* @param {String/HTMLElement} content String/element representing
* the new body content
*/
this.changeBodyEvent = this.createEvent(EVENT_TYPES.CHANGE_BODY);
this.changeBodyEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the footer content of the Module
* is modified
* @event changeFooterEvent
* @param {String/HTMLElement} content String/element representing
* the new footer content
*/
this.changeFooterEvent = this.createEvent(EVENT_TYPES.CHANGE_FOOTER);
this.changeFooterEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the content of the Module is modified
* @event changeContentEvent
*/
this.changeContentEvent = this.createEvent(EVENT_TYPES.CHANGE_CONTENT);
this.changeContentEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the Module is destroyed
* @event destroyEvent
*/
this.destroyEvent = this.createEvent(EVENT_TYPES.DESTROY);
this.destroyEvent.signature = SIGNATURE;
/**
* CustomEvent fired before the Module is shown
* @event beforeShowEvent
*/
this.beforeShowEvent = this.createEvent(EVENT_TYPES.BEFORE_SHOW);
this.beforeShowEvent.signature = SIGNATURE;
/**
* CustomEvent fired after the Module is shown
* @event showEvent
*/
this.showEvent = this.createEvent(EVENT_TYPES.SHOW);
this.showEvent.signature = SIGNATURE;
/**
* CustomEvent fired before the Module is hidden
* @event beforeHideEvent
*/
this.beforeHideEvent = this.createEvent(EVENT_TYPES.BEFORE_HIDE);
this.beforeHideEvent.signature = SIGNATURE;
/**
* CustomEvent fired after the Module is hidden
* @event hideEvent
*/
this.hideEvent = this.createEvent(EVENT_TYPES.HIDE);
this.hideEvent.signature = SIGNATURE;
},
/**
* String representing the current user-agent platform
* @property platform
* @type String
*/
platform: function () {
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1) {
return "windows";
} else if (ua.indexOf("macintosh") != -1) {
return "mac";
} else {
return false;
}
}(),
/**
* String representing the user-agent of the browser
* @deprecated Use YAHOO.env.ua
* @property browser
* @type String
*/
browser: function () {
var ua = navigator.userAgent.toLowerCase();
/*
Check Opera first in case of spoof and check Safari before
Gecko since Safari's user agent string includes "like Gecko"
*/
if (ua.indexOf('opera') != -1) {
return 'opera';
} else if (ua.indexOf('msie 7') != -1) {
return 'ie7';
} else if (ua.indexOf('msie') != -1) {
return 'ie';
} else if (ua.indexOf('safari') != -1) {
return 'safari';
} else if (ua.indexOf('gecko') != -1) {
return 'gecko';
} else {
return false;
}
}(),
/**
* Boolean representing whether or not the current browsing context is
* secure (https)
* @property isSecure
* @type Boolean
*/
isSecure: function () {
if (window.location.href.toLowerCase().indexOf("https") === 0) {
return true;
} else {
return false;
}
}(),
/**
* Initializes the custom events for Module which are fired
* automatically at appropriate times by the Module class.
*/
initDefaultConfig: function () {
// Add properties //
/**
* Specifies whether the Module is visible on the page.
* @config visible
* @type Boolean
* @default true
*/
this.cfg.addProperty(DEFAULT_CONFIG.VISIBLE.key, {
handler: this.configVisible,
value: DEFAULT_CONFIG.VISIBLE.value,
validator: DEFAULT_CONFIG.VISIBLE.validator
});
/**
* <p>
* Object or array of objects representing the ContainerEffect
* classes that are active for animating the container.
* </p>
* <p>
* <strong>NOTE:</strong> Although this configuration
* property is introduced at the Module level, an out of the box
* implementation is not shipped for the Module class so setting
* the proroperty on the Module class has no effect. The Overlay
* class is the first class to provide out of the box ContainerEffect
* support.
* </p>
* @config effect
* @type Object
* @default null
*/
this.cfg.addProperty(DEFAULT_CONFIG.EFFECT.key, {
suppressEvent: DEFAULT_CONFIG.EFFECT.suppressEvent,
supercedes: DEFAULT_CONFIG.EFFECT.supercedes
});
/**
* Specifies whether to create a special proxy iframe to monitor
* for user font resizing in the document
* @config monitorresize
* @type Boolean
* @default true
*/
this.cfg.addProperty(DEFAULT_CONFIG.MONITOR_RESIZE.key, {
handler: this.configMonitorResize,
value: DEFAULT_CONFIG.MONITOR_RESIZE.value
});
/**
* Specifies if the module should be rendered as the first child
* of document.body or appended as the last child when render is called
* with document.body as the "appendToNode".
* <p>
* Appending to the body while the DOM is still being constructed can
* lead to Operation Aborted errors in IE hence this flag is set to
* false by default.
* </p>
*
* @config appendtodocumentbody
* @type Boolean
* @default false
*/
this.cfg.addProperty(DEFAULT_CONFIG.APPEND_TO_DOCUMENT_BODY.key, {
value: DEFAULT_CONFIG.APPEND_TO_DOCUMENT_BODY.value
});
},
/**
* The Module class's initialization method, which is executed for
* Module 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.
* <p>
* If the element passed in does not have an id, one will be generated
* for it.
* </p>
* @method init
* @param {String} el The element ID representing the Module <em>OR</em>
* @param {HTMLElement} el The element representing the Module
* @param {Object} userConfig The configuration Object literal
* containing the configuration that should be set for this module.
* See configuration documentation for more details.
*/
init: function (el, userConfig) {
var elId, child;
this.initEvents();
this.beforeInitEvent.fire(Module);
/**
* The Module's Config object used for monitoring
* configuration properties.
* @property cfg
* @type YAHOO.util.Config
*/
this.cfg = new Config(this);
if (this.isSecure) {
this.imageRoot = Module.IMG_ROOT_SSL;
}
if (typeof el == "string") {
elId = el;
el = document.getElementById(el);
if (! el) {
el = (createModuleTemplate()).cloneNode(false);
el.id = elId;
}
}
this.id = Dom.generateId(el);
this.element = el;
child = this.element.firstChild;
if (child) {
var fndHd = false, fndBd = false, fndFt = false;
do {
// We're looking for elements
if (1 == child.nodeType) {
if (!fndHd && Dom.hasClass(child, Module.CSS_HEADER)) {
this.header = child;
fndHd = true;
} else if (!fndBd && Dom.hasClass(child, Module.CSS_BODY)) {
this.body = child;
fndBd = true;
} else if (!fndFt && Dom.hasClass(child, Module.CSS_FOOTER)){
this.footer = child;
fndFt = true;
}
}
} while ((child = child.nextSibling));
}
this.initDefaultConfig();
Dom.addClass(this.element, Module.CSS_MODULE);
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
/*
Subscribe to the fireQueue() method of Config so that any
queued configuration changes are excecuted upon render of
the Module
*/
if (!Config.alreadySubscribed(this.renderEvent, this.cfg.fireQueue, this.cfg)) {
this.renderEvent.subscribe(this.cfg.fireQueue, this.cfg, true);
}
this.initEvent.fire(Module);
},
/**
* Initialize an empty IFRAME that is placed out of the visible area
* that can be used to detect text resize.
* @method initResizeMonitor
*/
initResizeMonitor: function () {
var isGeckoWin = (UA.gecko && this.platform == "windows");
if (isGeckoWin) {
// Help prevent spinning loading icon which
// started with FireFox 2.0.0.8/Win
var self = this;
setTimeout(function(){self._initResizeMonitor();}, 0);
} else {
this._initResizeMonitor();
}
},
/**
* Create and initialize the text resize monitoring iframe.
*
* @protected
* @method _initResizeMonitor
*/
_initResizeMonitor : function() {
var oDoc,
oIFrame,
sHTML;
function fireTextResize() {
Module.textResizeEvent.fire();
}
if (!UA.opera) {
oIFrame = Dom.get("_yuiResizeMonitor");
var supportsCWResize = this._supportsCWResize();
if (!oIFrame) {
oIFrame = document.createElement("iframe");
if (this.isSecure && Module.RESIZE_MONITOR_SECURE_URL && UA.ie) {
oIFrame.src = Module.RESIZE_MONITOR_SECURE_URL;
}
if (!supportsCWResize) {
// Can't monitor on contentWindow, so fire from inside iframe
sHTML = ["<html><head><script ",
"type=\"text/javascript\">",
"window.onresize=function(){window.parent.",
"YAHOO.widget.Module.textResizeEvent.",
"fire();};<",
"\/script></head>",
"<body></body></html>"].join('');
oIFrame.src = "data:text/html;charset=utf-8," + encodeURIComponent(sHTML);
}
oIFrame.id = "_yuiResizeMonitor";
oIFrame.title = "Text Resize Monitor";
/*
Need to set "position" property before inserting the
iframe into the document or Safari's status bar will
forever indicate the iframe is loading
(See YUILibrary bug #1723064)
*/
oIFrame.style.position = "absolute";
oIFrame.style.visibility = "hidden";
var db = document.body,
fc = db.firstChild;
if (fc) {
db.insertBefore(oIFrame, fc);
} else {
db.appendChild(oIFrame);
}
// Setting the background color fixes an issue with IE6/IE7, where
// elements in the DOM, with -ve margin-top which positioned them
// offscreen (so they would be overlapped by the iframe and its -ve top
// setting), would have their -ve margin-top ignored, when the iframe
// was added.
oIFrame.style.backgroundColor = "transparent";
oIFrame.style.borderWidth = "0";
oIFrame.style.width = "2em";
oIFrame.style.height = "2em";
oIFrame.style.left = "0";
oIFrame.style.top = (-1 * (oIFrame.offsetHeight + Module.RESIZE_MONITOR_BUFFER)) + "px";
oIFrame.style.visibility = "visible";
/*
Don't open/close the document for Gecko like we used to, since it
leads to duplicate cookies. (See YUILibrary bug #1721755)
*/
if (UA.webkit) {
oDoc = oIFrame.contentWindow.document;
oDoc.open();
oDoc.close();
}
}
if (oIFrame && oIFrame.contentWindow) {
Module.textResizeEvent.subscribe(this.onDomResize, this, true);
if (!Module.textResizeInitialized) {
if (supportsCWResize) {
if (!Event.on(oIFrame.contentWindow, "resize", fireTextResize)) {
/*
This will fail in IE if document.domain has
changed, so we must change the listener to
use the oIFrame element instead
*/
Event.on(oIFrame, "resize", fireTextResize);
}
}
Module.textResizeInitialized = true;
}
this.resizeMonitor = oIFrame;
}
}
},
/**
* Text resize monitor helper method.
* Determines if the browser supports resize events on iframe content windows.
*
* @private
* @method _supportsCWResize
*/
_supportsCWResize : function() {
/*
Gecko 1.8.0 (FF1.5), 1.8.1.0-5 (FF2) won't fire resize on contentWindow.
Gecko 1.8.1.6+ (FF2.0.0.6+) and all other browsers will fire resize on contentWindow.
We don't want to start sniffing for patch versions, so fire textResize the same
way on all FF2 flavors
*/
var bSupported = true;
if (UA.gecko && UA.gecko <= 1.8) {
bSupported = false;
}
return bSupported;
},
/**
* Event handler fired when the resize monitor element is resized.
* @method onDomResize
* @param {DOMEvent} e The DOM resize event
* @param {Object} obj The scope object passed to the handler
*/
onDomResize: function (e, obj) {
var nTop = -1 * (this.resizeMonitor.offsetHeight + Module.RESIZE_MONITOR_BUFFER);
this.resizeMonitor.style.top = nTop + "px";
this.resizeMonitor.style.left = "0";
},
/**
* Sets the Module's header content to the string specified, or appends
* the passed element to the header. If no header is present, one will
* be automatically created. An empty string can be passed to the method
* to clear the contents of the header.
*
* @method setHeader
* @param {String} headerContent The string used to set the header.
* As a convenience, non HTMLElement objects can also be passed into
* the method, and will be treated as strings, with the header innerHTML
* set to their default toString implementations.
* <em>OR</em>
* @param {HTMLElement} headerContent The HTMLElement to append to
* <em>OR</em>
* @param {DocumentFragment} headerContent The document fragment
* containing elements which are to be added to the header
*/
setHeader: function (headerContent) {
var oHeader = this.header || (this.header = createHeader());
if (headerContent.nodeName) {
oHeader.innerHTML = "";
oHeader.appendChild(headerContent);
} else {
oHeader.innerHTML = headerContent;
}
if (this._rendered) {
this._renderHeader();
}
this.changeHeaderEvent.fire(headerContent);
this.changeContentEvent.fire();
},
/**
* Appends the passed element to the header. If no header is present,
* one will be automatically created.
* @method appendToHeader
* @param {HTMLElement | DocumentFragment} element The element to
* append to the header. In the case of a document fragment, the
* children of the fragment will be appended to the header.
*/
appendToHeader: function (element) {
var oHeader = this.header || (this.header = createHeader());
oHeader.appendChild(element);
this.changeHeaderEvent.fire(element);
this.changeContentEvent.fire();
},
/**
* Sets the Module's body content to the HTML specified.
*
* If no body is present, one will be automatically created.
*
* An empty string can be passed to the method to clear the contents of the body.
* @method setBody
* @param {String} bodyContent The HTML used to set the body.
* As a convenience, non HTMLElement objects can also be passed into
* the method, and will be treated as strings, with the body innerHTML
* set to their default toString implementations.
* <em>OR</em>
* @param {HTMLElement} bodyContent The HTMLElement to add as the first and only
* child of the body element.
* <em>OR</em>
* @param {DocumentFragment} bodyContent The document fragment
* containing elements which are to be added to the body
*/
setBody: function (bodyContent) {
var oBody = this.body || (this.body = createBody());
if (bodyContent.nodeName) {
oBody.innerHTML = "";
oBody.appendChild(bodyContent);
} else {
oBody.innerHTML = bodyContent;
}
if (this._rendered) {
this._renderBody();
}
this.changeBodyEvent.fire(bodyContent);
this.changeContentEvent.fire();
},
/**
* Appends the passed element to the body. If no body is present, one
* will be automatically created.
* @method appendToBody
* @param {HTMLElement | DocumentFragment} element The element to
* append to the body. In the case of a document fragment, the
* children of the fragment will be appended to the body.
*
*/
appendToBody: function (element) {
var oBody = this.body || (this.body = createBody());
oBody.appendChild(element);
this.changeBodyEvent.fire(element);
this.changeContentEvent.fire();
},
/**
* Sets the Module's footer content to the HTML specified, or appends
* the passed element to the footer. If no footer is present, one will
* be automatically created. An empty string can be passed to the method
* to clear the contents of the footer.
* @method setFooter
* @param {String} footerContent The HTML used to set the footer
* As a convenience, non HTMLElement objects can also be passed into
* the method, and will be treated as strings, with the footer innerHTML
* set to their default toString implementations.
* <em>OR</em>
* @param {HTMLElement} footerContent The HTMLElement to append to
* the footer
* <em>OR</em>
* @param {DocumentFragment} footerContent The document fragment containing
* elements which are to be added to the footer
*/
setFooter: function (footerContent) {
var oFooter = this.footer || (this.footer = createFooter());
if (footerContent.nodeName) {
oFooter.innerHTML = "";
oFooter.appendChild(footerContent);
} else {
oFooter.innerHTML = footerContent;
}
if (this._rendered) {
this._renderFooter();
}
this.changeFooterEvent.fire(footerContent);
this.changeContentEvent.fire();
},
/**
* Appends the passed element to the footer. If no footer is present,
* one will be automatically created.
* @method appendToFooter
* @param {HTMLElement | DocumentFragment} element The element to
* append to the footer. In the case of a document fragment, the
* children of the fragment will be appended to the footer
*/
appendToFooter: function (element) {
var oFooter = this.footer || (this.footer = createFooter());
oFooter.appendChild(element);
this.changeFooterEvent.fire(element);
this.changeContentEvent.fire();
},
/**
* Renders the Module by inserting the elements that are not already
* in the main Module into their correct places. Optionally appends
* the Module to the specified node prior to the render's execution.
* <p>
* For Modules 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.
* </p>
* <p>
* NOTE: As of 2.3.1, if the appendToNode is the document's body element
* then the module is rendered as the first child of the body element,
* and not appended to it, to avoid Operation Aborted errors in IE when
* rendering the module before window's load event is fired. You can
* use the appendtodocumentbody configuration property to change this
* to append to document.body if required.
* </p>
* @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
* @param {HTMLElement} moduleElement OPTIONAL. The element that
* represents the actual Standard Module container.
* @return {Boolean} Success or failure of the render
*/
render: function (appendToNode, moduleElement) {
var me = this;
function appendTo(parentNode) {
if (typeof parentNode == "string") {
parentNode = document.getElementById(parentNode);
}
if (parentNode) {
me._addToParent(parentNode, me.element);
me.appendEvent.fire();
}
}
this.beforeRenderEvent.fire();
if (! moduleElement) {
moduleElement = this.element;
}
if (appendToNode) {
appendTo(appendToNode);
} else {
// No node was passed in. If the element is not already in the Dom, this fails
if (! Dom.inDocument(this.element)) {
YAHOO.log("Render failed. Must specify appendTo node if " + " Module isn't already in the DOM.", "error");
return false;
}
}
this._renderHeader(moduleElement);
this._renderBody(moduleElement);
this._renderFooter(moduleElement);
this._rendered = true;
this.renderEvent.fire();
return true;
},
/**
* Renders the currently set header into it's proper position under the
* module element. If the module element is not provided, "this.element"
* is used.
*
* @method _renderHeader
* @protected
* @param {HTMLElement} moduleElement Optional. A reference to the module element
*/
_renderHeader: function(moduleElement){
moduleElement = moduleElement || this.element;
// Need to get everything into the DOM if it isn't already
if (this.header && !Dom.inDocument(this.header)) {
// There is a header, but it's not in the DOM yet. Need to add it.
var firstChild = moduleElement.firstChild;
if (firstChild) {
moduleElement.insertBefore(this.header, firstChild);
} else {
moduleElement.appendChild(this.header);
}
}
},
/**
* Renders the currently set body into it's proper position under the
* module element. If the module element is not provided, "this.element"
* is used.
*
* @method _renderBody
* @protected
* @param {HTMLElement} moduleElement Optional. A reference to the module element.
*/
_renderBody: function(moduleElement){
moduleElement = moduleElement || this.element;
if (this.body && !Dom.inDocument(this.body)) {
// There is a body, but it's not in the DOM yet. Need to add it.
if (this.footer && Dom.isAncestor(moduleElement, this.footer)) {
moduleElement.insertBefore(this.body, this.footer);
} else {
moduleElement.appendChild(this.body);
}
}
},
/**
* Renders the currently set footer into it's proper position under the
* module element. If the module element is not provided, "this.element"
* is used.
*
* @method _renderFooter
* @protected
* @param {HTMLElement} moduleElement Optional. A reference to the module element
*/
_renderFooter: function(moduleElement){
moduleElement = moduleElement || this.element;
if (this.footer && !Dom.inDocument(this.footer)) {
// There is a footer, but it's not in the DOM yet. Need to add it.
moduleElement.appendChild(this.footer);
}
},
/**
* Removes the Module element from the DOM and sets all child elements
* to null.
* @method destroy
*/
destroy: function () {
var parent;
if (this.element) {
Event.purgeElement(this.element, true);
parent = this.element.parentNode;
}
if (parent) {
parent.removeChild(this.element);
}
this.element = null;
this.header = null;
this.body = null;
this.footer = null;
Module.textResizeEvent.unsubscribe(this.onDomResize, this);
this.cfg.destroy();
this.cfg = null;
this.destroyEvent.fire();
},
/**
* Shows the Module element by setting the visible configuration
* property to true. Also fires two events: beforeShowEvent prior to
* the visibility change, and showEvent after.
* @method show
*/
show: function () {
this.cfg.setProperty("visible", true);
},
/**
* Hides the Module element by setting the visible configuration
* property to false. Also fires two events: beforeHideEvent prior to
* the visibility change, and hideEvent after.
* @method hide
*/
hide: function () {
this.cfg.setProperty("visible", false);
},
// BUILT-IN EVENT HANDLERS FOR MODULE //
/**
* Default event handler for changing the visibility property of a
* Module. By default, this is achieved by switching the "display" style
* between "block" and "none".
* This method is responsible for firing showEvent and hideEvent.
* @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.
* @method configVisible
*/
configVisible: function (type, args, obj) {
var visible = args[0];
if (visible) {
this.beforeShowEvent.fire();
Dom.setStyle(this.element, "display", "block");
this.showEvent.fire();
} else {
this.beforeHideEvent.fire();
Dom.setStyle(this.element, "display", "none");
this.hideEvent.fire();
}
},
/**
* Default event handler for the "monitorresize" configuration property
* @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.
* @method configMonitorResize
*/
configMonitorResize: function (type, args, obj) {
var monitor = args[0];
if (monitor) {
this.initResizeMonitor();
} else {
Module.textResizeEvent.unsubscribe(this.onDomResize, this, true);
this.resizeMonitor = null;
}
},
/**
* This method is a protected helper, used when constructing the DOM structure for the module
* to account for situations which may cause Operation Aborted errors in IE. It should not
* be used for general DOM construction.
* <p>
* If the parentNode is not document.body, the element is appended as the last element.
* </p>
* <p>
* If the parentNode is document.body the element is added as the first child to help
* prevent Operation Aborted errors in IE.
* </p>
*
* @param {parentNode} The HTML element to which the element will be added
* @param {element} The HTML element to be added to parentNode's children
* @method _addToParent
* @protected
*/
_addToParent: function(parentNode, element) {
if (!this.cfg.getProperty("appendtodocumentbody") && parentNode === document.body && parentNode.firstChild) {
parentNode.insertBefore(element, parentNode.firstChild);
} else {
parentNode.appendChild(element);
}
},
/**
* Returns a String representation of the Object.
* @method toString
* @return {String} The string representation of the Module
*/
toString: function () {
return "Module " + this.id;
}
};
YAHOO.lang.augmentProto(Module, YAHOO.util.EventProvider);
}());
(function () {
/**
* Overlay is a Module that is absolutely positioned above the page flow. It
* has convenience methods for positioning and sizing, as well as options for
* controlling zIndex and constraining the Overlay's position to the current
* visible viewport. Overlay also contains a dynamicly generated IFRAME which
* is placed beneath it for Internet Explorer 6 and 5.x so that it will be
* properly rendered above SELECT elements.
* @namespace YAHOO.widget
* @class Overlay
* @extends YAHOO.widget.Module
* @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.
* @constructor
*/
YAHOO.widget.Overlay = function (el, userConfig) {
YAHOO.widget.Overlay.superclass.constructor.call(this, el, userConfig);
};
var Lang = YAHOO.lang,
CustomEvent = YAHOO.util.CustomEvent,
Module = YAHOO.widget.Module,
Event = YAHOO.util.Event,
Dom = YAHOO.util.Dom,
Config = YAHOO.util.Config,
UA = YAHOO.env.ua,
Overlay = YAHOO.widget.Overlay,
_SUBSCRIBE = "subscribe",
_UNSUBSCRIBE = "unsubscribe",
_CONTAINED = "contained",
m_oIFrameTemplate,
/**
* Constant representing the name of the Overlay's events
* @property EVENT_TYPES
* @private
* @final
* @type Object
*/
EVENT_TYPES = {
"BEFORE_MOVE": "beforeMove",
"MOVE": "move"
},
/**
* Constant representing the Overlay's configuration properties
* @property DEFAULT_CONFIG
* @private
* @final
* @type Object
*/
DEFAULT_CONFIG = {
"X": {
key: "x",
validator: Lang.isNumber,
suppressEvent: true,
supercedes: ["iframe"]
},
"Y": {
key: "y",
validator: Lang.isNumber,
suppressEvent: true,
supercedes: ["iframe"]
},
"XY": {
key: "xy",
suppressEvent: true,
supercedes: ["iframe"]
},
"CONTEXT": {
key: "context",
suppressEvent: true,
supercedes: ["iframe"]
},
"FIXED_CENTER": {
key: "fixedcenter",
value: false,
supercedes: ["iframe", "visible"]
},
"WIDTH": {
key: "width",
suppressEvent: true,
supercedes: ["context", "fixedcenter", "iframe"]
},
"HEIGHT": {
key: "height",
suppressEvent: true,
supercedes: ["context", "fixedcenter", "iframe"]
},
"AUTO_FILL_HEIGHT" : {
key: "autofillheight",
supercedes: ["height"],
value:"body"
},
"ZINDEX": {
key: "zindex",
value: null
},
"CONSTRAIN_TO_VIEWPORT": {
key: "constraintoviewport",
value: false,
validator: Lang.isBoolean,
supercedes: ["iframe", "x", "y", "xy"]
},
"IFRAME": {
key: "iframe",
value: (UA.ie == 6 ? true : false),
validator: Lang.isBoolean,
supercedes: ["zindex"]
},
"PREVENT_CONTEXT_OVERLAP": {
key: "preventcontextoverlap",
value: false,
validator: Lang.isBoolean,
supercedes: ["constraintoviewport"]
}
};
/**
* The URL that will be placed in the iframe
* @property YAHOO.widget.Overlay.IFRAME_SRC
* @static
* @final
* @type String
*/
Overlay.IFRAME_SRC = "javascript:false;";
/**
* Number representing how much the iframe shim should be offset from each
* side of an Overlay instance, in pixels.
* @property YAHOO.widget.Overlay.IFRAME_SRC
* @default 3
* @static
* @final
* @type Number
*/
Overlay.IFRAME_OFFSET = 3;
/**
* Number representing the minimum distance an Overlay instance should be
* positioned relative to the boundaries of the browser's viewport, in pixels.
* @property YAHOO.widget.Overlay.VIEWPORT_OFFSET
* @default 10
* @static
* @final
* @type Number
*/
Overlay.VIEWPORT_OFFSET = 10;
/**
* Constant representing the top left corner of an element, used for
* configuring the context element alignment
* @property YAHOO.widget.Overlay.TOP_LEFT
* @static
* @final
* @type String
*/
Overlay.TOP_LEFT = "tl";
/**
* Constant representing the top right corner of an element, used for
* configuring the context element alignment
* @property YAHOO.widget.Overlay.TOP_RIGHT
* @static
* @final
* @type String
*/
Overlay.TOP_RIGHT = "tr";
/**
* Constant representing the top bottom left corner of an element, used for
* configuring the context element alignment
* @property YAHOO.widget.Overlay.BOTTOM_LEFT
* @static
* @final
* @type String
*/
Overlay.BOTTOM_LEFT = "bl";
/**
* Constant representing the bottom right corner of an element, used for
* configuring the context element alignment
* @property YAHOO.widget.Overlay.BOTTOM_RIGHT
* @static
* @final
* @type String
*/
Overlay.BOTTOM_RIGHT = "br";
Overlay.PREVENT_OVERLAP_X = {
"tltr": true,
"blbr": true,
"brbl": true,
"trtl": true
};
Overlay.PREVENT_OVERLAP_Y = {
"trbr": true,
"tlbl": true,
"bltl": true,
"brtr": true
};
/**
* Constant representing the default CSS class used for an Overlay
* @property YAHOO.widget.Overlay.CSS_OVERLAY
* @static
* @final
* @type String
*/
Overlay.CSS_OVERLAY = "yui-overlay";
/**
* Constant representing the default hidden CSS class used for an Overlay. This class is
* applied to the overlay's outer DIV whenever it's hidden.
*
* @property YAHOO.widget.Overlay.CSS_HIDDEN
* @static
* @final
* @type String
*/
Overlay.CSS_HIDDEN = "yui-overlay-hidden";
/**
* Constant representing the default CSS class used for an Overlay iframe shim.
*
* @property YAHOO.widget.Overlay.CSS_IFRAME
* @static
* @final
* @type String
*/
Overlay.CSS_IFRAME = "yui-overlay-iframe";
/**
* Constant representing the names of the standard module elements
* used in the overlay.
* @property YAHOO.widget.Overlay.STD_MOD_RE
* @static
* @final
* @type RegExp
*/
Overlay.STD_MOD_RE = /^\s*?(body|footer|header)\s*?$/i;
/**
* A singleton CustomEvent used for reacting to the DOM event for
* window scroll
* @event YAHOO.widget.Overlay.windowScrollEvent
*/
Overlay.windowScrollEvent = new CustomEvent("windowScroll");
/**
* A singleton CustomEvent used for reacting to the DOM event for
* window resize
* @event YAHOO.widget.Overlay.windowResizeEvent
*/
Overlay.windowResizeEvent = new CustomEvent("windowResize");
/**
* The DOM event handler used to fire the CustomEvent for window scroll
* @method YAHOO.widget.Overlay.windowScrollHandler
* @static
* @param {DOMEvent} e The DOM scroll event
*/
Overlay.windowScrollHandler = function (e) {
var t = Event.getTarget(e);
// - Webkit (Safari 2/3) and Opera 9.2x bubble scroll events from elements to window
// - FF2/3 and IE6/7, Opera 9.5x don't bubble scroll events from elements to window
// - IE doesn't recognize scroll registered on the document.
//
// Also, when document view is scrolled, IE doesn't provide a target,
// rest of the browsers set target to window.document, apart from opera
// which sets target to window.
if (!t || t === window || t === window.document) {
if (UA.ie) {
if (! window.scrollEnd) {
window.scrollEnd = -1;
}
clearTimeout(window.scrollEnd);
window.scrollEnd = setTimeout(function () {
Overlay.windowScrollEvent.fire();
}, 1);
} else {
Overlay.windowScrollEvent.fire();
}
}
};
/**
* The DOM event handler used to fire the CustomEvent for window resize
* @method YAHOO.widget.Overlay.windowResizeHandler
* @static
* @param {DOMEvent} e The DOM resize event
*/
Overlay.windowResizeHandler = function (e) {
if (UA.ie) {
if (! window.resizeEnd) {
window.resizeEnd = -1;
}
clearTimeout(window.resizeEnd);
window.resizeEnd = setTimeout(function () {
Overlay.windowResizeEvent.fire();
}, 100);
} else {
Overlay.windowResizeEvent.fire();
}
};
/**
* A boolean that indicated whether the window resize and scroll events have
* already been subscribed to.
* @property YAHOO.widget.Overlay._initialized
* @private
* @type Boolean
*/
Overlay._initialized = null;
if (Overlay._initialized === null) {
Event.on(window, "scroll", Overlay.windowScrollHandler);
Event.on(window, "resize", Overlay.windowResizeHandler);
Overlay._initialized = true;
}
/**
* Internal map of special event types, which are provided
* by the instance. It maps the event type to the custom event
* instance. Contains entries for the "windowScroll", "windowResize" and
* "textResize" static container events.
*
* @property YAHOO.widget.Overlay._TRIGGER_MAP
* @type Object
* @static
* @private
*/
Overlay._TRIGGER_MAP = {
"windowScroll" : Overlay.windowScrollEvent,
"windowResize" : Overlay.windowResizeEvent,
"textResize" : Module.textResizeEvent
};
YAHOO.extend(Overlay, Module, {
/**
* <p>
* Array of default event types which will trigger
* context alignment for the Overlay class.
* </p>
* <p>The array is empty by default for Overlay,
* but maybe populated in future releases, so classes extending
* Overlay which need to define their own set of CONTEXT_TRIGGERS
* should concatenate their super class's prototype.CONTEXT_TRIGGERS
* value with their own array of values.
* </p>
* <p>
* E.g.:
* <code>CustomOverlay.prototype.CONTEXT_TRIGGERS = YAHOO.widget.Overlay.prototype.CONTEXT_TRIGGERS.concat(["windowScroll"]);</code>
* </p>
*
* @property CONTEXT_TRIGGERS
* @type Array
* @final
*/
CONTEXT_TRIGGERS : [],
/**
* 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
*/
Overlay.superclass.init.call(this, el/*, userConfig*/);
this.beforeInitEvent.fire(Overlay);
Dom.addClass(this.element, Overlay.CSS_OVERLAY);
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
if (this.platform == "mac" && UA.gecko) {
if (! Config.alreadySubscribed(this.showEvent,
this.showMacGeckoScrollbars, this)) {
this.showEvent.subscribe(this.showMacGeckoScrollbars,
this, true);
}
if (! Config.alreadySubscribed(this.hideEvent,
this.hideMacGeckoScrollbars, this)) {
this.hideEvent.subscribe(this.hideMacGeckoScrollbars,
this, true);
}
}
this.initEvent.fire(Overlay);
},
/**
* Initializes the custom events for Overlay which are fired
* automatically at appropriate times by the Overlay class.
* @method initEvents
*/
initEvents: function () {
Overlay.superclass.initEvents.call(this);
var SIGNATURE = CustomEvent.LIST;
/**
* CustomEvent fired before the Overlay is moved.
* @event beforeMoveEvent
* @param {Number} x x coordinate
* @param {Number} y y coordinate
*/
this.beforeMoveEvent = this.createEvent(EVENT_TYPES.BEFORE_MOVE);
this.beforeMoveEvent.signature = SIGNATURE;
/**
* CustomEvent fired after the Overlay is moved.
* @event moveEvent
* @param {Number} x x coordinate
* @param {Number} y y coordinate
*/
this.moveEvent = this.createEvent(EVENT_TYPES.MOVE);
this.moveEvent.signature = SIGNATURE;
},
/**
* Initializes the class's configurable properties which can be changed
* using the Overlay's Config object (cfg).
* @method initDefaultConfig
*/
initDefaultConfig: function () {
Overlay.superclass.initDefaultConfig.call(this);
var cfg = this.cfg;
// Add overlay config properties //
/**
* The absolute x-coordinate position of the Overlay
* @config x
* @type Number
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.X.key, {
handler: this.configX,
validator: DEFAULT_CONFIG.X.validator,
suppressEvent: DEFAULT_CONFIG.X.suppressEvent,
supercedes: DEFAULT_CONFIG.X.supercedes
});
/**
* The absolute y-coordinate position of the Overlay
* @config y
* @type Number
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.Y.key, {
handler: this.configY,
validator: DEFAULT_CONFIG.Y.validator,
suppressEvent: DEFAULT_CONFIG.Y.suppressEvent,
supercedes: DEFAULT_CONFIG.Y.supercedes
});
/**
* An array with the absolute x and y positions of the Overlay
* @config xy
* @type Number[]
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.XY.key, {
handler: this.configXY,
suppressEvent: DEFAULT_CONFIG.XY.suppressEvent,
supercedes: DEFAULT_CONFIG.XY.supercedes
});
/**
* <p>
* The array of context arguments for context-sensitive positioning.
* </p>
*
* <p>
* The format of the array is: <code>[contextElementOrId, overlayCorner, contextCorner, arrayOfTriggerEvents (optional), xyOffset (optional)]</code>, the
* the 5 array elements described in detail below:
* </p>
*
* <dl>
* <dt>contextElementOrId <String|HTMLElement></dt>
* <dd>A reference to the context element to which the overlay should be aligned (or it's id).</dd>
* <dt>overlayCorner <String></dt>
* <dd>The corner of the overlay which is to be used for alignment. This corner will be aligned to the
* corner of the context element defined by the "contextCorner" entry which follows. Supported string values are:
* "tr" (top right), "tl" (top left), "br" (bottom right), or "bl" (bottom left).</dd>
* <dt>contextCorner <String></dt>
* <dd>The corner of the context element which is to be used for alignment. Supported string values are the same ones listed for the "overlayCorner" entry above.</dd>
* <dt>arrayOfTriggerEvents (optional) <Array[String|CustomEvent]></dt>
* <dd>
* <p>
* By default, context alignment is a one time operation, aligning the Overlay to the context element when context configuration property is set, or when the <a href="#method_align">align</a>
* method is invoked. However, you can use the optional "arrayOfTriggerEvents" entry to define the list of events which should force the overlay to re-align itself with the context element.
* This is useful in situations where the layout of the document may change, resulting in the context element's position being modified.
* </p>
* <p>
* The array can contain either event type strings for events the instance publishes (e.g. "beforeShow") or CustomEvent instances. Additionally the following
* 3 static container event types are also currently supported : <code>"windowResize", "windowScroll", "textResize"</code> (defined in <a href="#property__TRIGGER_MAP">_TRIGGER_MAP</a> private property).
* </p>
* </dd>
* <dt>xyOffset <Number[]></dt>
* <dd>
* A 2 element Array specifying the X and Y pixel amounts by which the Overlay should be offset from the aligned corner. e.g. [5,0] offsets the Overlay 5 pixels to the left, <em>after</em> aligning the given context corners.
* NOTE: If using this property and no triggers need to be defined, the arrayOfTriggerEvents property should be set to null to maintain correct array positions for the arguments.
* </dd>
* </dl>
*
* <p>
* For example, setting this property to <code>["img1", "tl", "bl"]</code> will
* align the Overlay's top left corner to the bottom left corner of the
* context element with id "img1".
* </p>
* <p>
* Setting this property to <code>["img1", "tl", "bl", null, [0,5]</code> will
* align the Overlay's top left corner to the bottom left corner of the
* context element with id "img1", and then offset it by 5 pixels on the Y axis (providing a 5 pixel gap between the bottom of the context element and top of the overlay).
* </p>
* <p>
* Adding the optional trigger values: <code>["img1", "tl", "bl", ["beforeShow", "windowResize"], [0,5]]</code>,
* will re-align the overlay position, whenever the "beforeShow" or "windowResize" events are fired.
* </p>
*
* @config context
* @type Array
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.CONTEXT.key, {
handler: this.configContext,
suppressEvent: DEFAULT_CONFIG.CONTEXT.suppressEvent,
supercedes: DEFAULT_CONFIG.CONTEXT.supercedes
});
/**
* Determines whether or not the Overlay should be anchored
* to the center of the viewport.
*
* <p>This property can be set to:</p>
*
* <dl>
* <dt>true</dt>
* <dd>
* To enable fixed center positioning
* <p>
* When enabled, the overlay will
* be positioned in the center of viewport when initially displayed, and
* will remain in the center of the viewport whenever the window is
* scrolled or resized.
* </p>
* <p>
* If the overlay is too big for the viewport,
* it's top left corner will be aligned with the top left corner of the viewport.
* </p>
* </dd>
* <dt>false</dt>
* <dd>
* To disable fixed center positioning.
* <p>In this case the overlay can still be
* centered as a one-off operation, by invoking the <code>center()</code> method,
* however it will not remain centered when the window is scrolled/resized.
* </dd>
* <dt>"contained"<dt>
* <dd>To enable fixed center positioning, as with the <code>true</code> option.
* <p>However, unlike setting the property to <code>true</code>,
* when the property is set to <code>"contained"</code>, if the overlay is
* too big for the viewport, it will not get automatically centered when the
* user scrolls or resizes the window (until the window is large enough to contain the
* overlay). This is useful in cases where the Overlay has both header and footer
* UI controls which the user may need to access.
* </p>
* </dd>
* </dl>
*
* @config fixedcenter
* @type Boolean | String
* @default false
*/
cfg.addProperty(DEFAULT_CONFIG.FIXED_CENTER.key, {
handler: this.configFixedCenter,
value: DEFAULT_CONFIG.FIXED_CENTER.value,
validator: DEFAULT_CONFIG.FIXED_CENTER.validator,
supercedes: DEFAULT_CONFIG.FIXED_CENTER.supercedes
});
/**
* CSS width of the Overlay.
* @config width
* @type String
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.WIDTH.key, {
handler: this.configWidth,
suppressEvent: DEFAULT_CONFIG.WIDTH.suppressEvent,
supercedes: DEFAULT_CONFIG.WIDTH.supercedes
});
/**
* CSS height of the Overlay.
* @config height
* @type String
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.HEIGHT.key, {
handler: this.configHeight,
suppressEvent: DEFAULT_CONFIG.HEIGHT.suppressEvent,
supercedes: DEFAULT_CONFIG.HEIGHT.supercedes
});
/**
* Standard module element which should auto fill out the height of the Overlay if the height config property is set.
* Supported values are "header", "body", "footer".
*
* @config autofillheight
* @type String
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.AUTO_FILL_HEIGHT.key, {
handler: this.configAutoFillHeight,
value : DEFAULT_CONFIG.AUTO_FILL_HEIGHT.value,
validator : this._validateAutoFill,
supercedes: DEFAULT_CONFIG.AUTO_FILL_HEIGHT.supercedes
});
/**
* CSS z-index of the Overlay.
* @config zIndex
* @type Number
* @default null
*/
cfg.addProperty(DEFAULT_CONFIG.ZINDEX.key, {
handler: this.configzIndex,
value: DEFAULT_CONFIG.ZINDEX.value
});
/**
* True if the Overlay should be prevented from being positioned
* out of the viewport.
* @config constraintoviewport
* @type Boolean
* @default false
*/
cfg.addProperty(DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.key, {
handler: this.configConstrainToViewport,
value: DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.value,
validator: DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.validator,
supercedes: DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.supercedes
});
/**
* @config iframe
* @description Boolean indicating whether or not the Overlay should
* have an IFRAME shim; used to prevent SELECT elements from
* poking through an Overlay instance in IE6. When set to "true",
* the iframe shim is created when the Overlay instance is intially
* made visible.
* @type Boolean
* @default true for IE6 and below, false for all other browsers.
*/
cfg.addProperty(DEFAULT_CONFIG.IFRAME.key, {
handler: this.configIframe,
value: DEFAULT_CONFIG.IFRAME.value,
validator: DEFAULT_CONFIG.IFRAME.validator,
supercedes: DEFAULT_CONFIG.IFRAME.supercedes
});
/**
* @config preventcontextoverlap
* @description Boolean indicating whether or not the Overlay should overlap its
* context element (defined using the "context" configuration property) when the
* "constraintoviewport" configuration property is set to "true".
* @type Boolean
* @default false
*/
cfg.addProperty(DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.key, {
value: DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.value,
validator: DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.validator,
supercedes: DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.supercedes
});
},
/**
* Moves the Overlay to the specified position. This function is
* identical to calling this.cfg.setProperty("xy", [x,y]);
* @method moveTo
* @param {Number} x The Overlay's new x position
* @param {Number} y The Overlay's new y position
*/
moveTo: function (x, y) {
this.cfg.setProperty("xy", [x, y]);
},
/**
* Adds a CSS class ("hide-scrollbars") and removes a CSS class
* ("show-scrollbars") to the Overlay to fix a bug in Gecko on Mac OS X
* (https://bugzilla.mozilla.org/show_bug.cgi?id=187435)
* @method hideMacGeckoScrollbars
*/
hideMacGeckoScrollbars: function () {
Dom.replaceClass(this.element, "show-scrollbars", "hide-scrollbars");
},
/**
* Adds a CSS class ("show-scrollbars") and removes a CSS class
* ("hide-scrollbars") to the Overlay to fix a bug in Gecko on Mac OS X
* (https://bugzilla.mozilla.org/show_bug.cgi?id=187435)
* @method showMacGeckoScrollbars
*/
showMacGeckoScrollbars: function () {
Dom.replaceClass(this.element, "hide-scrollbars", "show-scrollbars");
},
/**
* Internal implementation to set the visibility of the overlay in the DOM.
*
* @method _setDomVisibility
* @param {boolean} visible Whether to show or hide the Overlay's outer element
* @protected
*/
_setDomVisibility : function(show) {
Dom.setStyle(this.element, "visibility", (show) ? "visible" : "hidden");
var hiddenClass = Overlay.CSS_HIDDEN;
if (show) {
Dom.removeClass(this.element, hiddenClass);
} else {
Dom.addClass(this.element, hiddenClass);
}
},
// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
/**
* The default event handler fired when the "visible" property is
* changed. This method is responsible for firing showEvent
* and hideEvent.
* @method configVisible
* @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.
*/
configVisible: function (type, args, obj) {
var visible = args[0],
currentVis = Dom.getStyle(this.element, "visibility"),
effect = this.cfg.getProperty("effect"),
effectInstances = [],
isMacGecko = (this.platform == "mac" && UA.gecko),
alreadySubscribed = Config.alreadySubscribed,
eff, ei, e, i, j, k, h,
nEffects,
nEffectInstances;
if (currentVis == "inherit") {
e = this.element.parentNode;
while (e.nodeType != 9 && e.nodeType != 11) {
currentVis = Dom.getStyle(e, "visibility");
if (currentVis != "inherit") {
break;
}
e = e.parentNode;
}
if (currentVis == "inherit") {
currentVis = "visible";
}
}
if (effect) {
if (effect instanceof Array) {
nEffects = effect.length;
for (i = 0; i < nEffects; i++) {
eff = effect[i];
effectInstances[effectInstances.length] =
eff.effect(this, eff.duration);
}
} else {
effectInstances[effectInstances.length] =
effect.effect(this, effect.duration);
}
}
if (visible) { // Show
if (isMacGecko) {
this.showMacGeckoScrollbars();
}
if (effect) { // Animate in
if (visible) { // Animate in if not showing
if (currentVis != "visible" || currentVis === "") {
this.beforeShowEvent.fire();
nEffectInstances = effectInstances.length;
for (j = 0; j < nEffectInstances; j++) {
ei = effectInstances[j];
if (j === 0 && !alreadySubscribed(
ei.animateInCompleteEvent,
this.showEvent.fire, this.showEvent)) {
/*
Delegate showEvent until end
of animateInComplete
*/
ei.animateInCompleteEvent.subscribe(
this.showEvent.fire, this.showEvent, true);
}
ei.animateIn();
}
}
}
} else { // Show
if (currentVis != "visible" || currentVis === "") {
this.beforeShowEvent.fire();
this._setDomVisibility(true);
this.cfg.refireEvent("iframe");
this.showEvent.fire();
} else {
this._setDomVisibility(true);
}
}
} else { // Hide
if (isMacGecko) {
this.hideMacGeckoScrollbars();
}
if (effect) { // Animate out if showing
if (currentVis == "visible") {
this.beforeHideEvent.fire();
nEffectInstances = effectInstances.length;
for (k = 0; k < nEffectInstances; k++) {
h = effectInstances[k];
if (k === 0 && !alreadySubscribed(
h.animateOutCompleteEvent, this.hideEvent.fire,
this.hideEvent)) {
/*
Delegate hideEvent until end
of animateOutComplete
*/
h.animateOutCompleteEvent.subscribe(
this.hideEvent.fire, this.hideEvent, true);
}
h.animateOut();
}
} else if (currentVis === "") {
this._setDomVisibility(false);
}
} else { // Simple hide
if (currentVis == "visible" || currentVis === "") {
this.beforeHideEvent.fire();
this._setDomVisibility(false);
this.hideEvent.fire();
} else {
this._setDomVisibility(false);
}
}
}
},
/**
* Fixed center event handler used for centering on scroll/resize, but only if
* the overlay is visible and, if "fixedcenter" is set to "contained", only if
* the overlay fits within the viewport.
*
* @method doCenterOnDOMEvent
*/
doCenterOnDOMEvent: function () {
var cfg = this.cfg,
fc = cfg.getProperty("fixedcenter");
if (cfg.getProperty("visible")) {
if (fc && (fc !== _CONTAINED || this.fitsInViewport())) {
this.center();
}
}
},
/**
* Determines if the Overlay (including the offset value defined by Overlay.VIEWPORT_OFFSET)
* will fit entirely inside the viewport, in both dimensions - width and height.
*
* @method fitsInViewport
* @return boolean true if the Overlay will fit, false if not
*/
fitsInViewport : function() {
var nViewportOffset = Overlay.VIEWPORT_OFFSET,
element = this.element,
elementWidth = element.offsetWidth,
elementHeight = element.offsetHeight,
viewportWidth = Dom.getViewportWidth(),
viewportHeight = Dom.getViewportHeight();
return ((elementWidth + nViewportOffset < viewportWidth) && (elementHeight + nViewportOffset < viewportHeight));
},
/**
* The default event handler fired when the "fixedcenter" property
* is changed.
* @method configFixedCenter
* @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.
*/
configFixedCenter: function (type, args, obj) {
var val = args[0],
alreadySubscribed = Config.alreadySubscribed,
windowResizeEvent = Overlay.windowResizeEvent,
windowScrollEvent = Overlay.windowScrollEvent;
if (val) {
this.center();
if (!alreadySubscribed(this.beforeShowEvent, this.center)) {
this.beforeShowEvent.subscribe(this.center);
}
if (!alreadySubscribed(windowResizeEvent, this.doCenterOnDOMEvent, this)) {
windowResizeEvent.subscribe(this.doCenterOnDOMEvent, this, true);
}
if (!alreadySubscribed(windowScrollEvent, this.doCenterOnDOMEvent, this)) {
windowScrollEvent.subscribe(this.doCenterOnDOMEvent, this, true);
}
} else {
this.beforeShowEvent.unsubscribe(this.center);
windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent, this);
windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent, this);
}
},
/**
* 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.element;
Dom.setStyle(el, "height", height);
this.cfg.refireEvent("iframe");
},
/**
* The default event handler fired when the "autofillheight" property is changed.
* @method configAutoFillHeight
*
* @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.
*/
configAutoFillHeight: function (type, args, obj) {
var fillEl = args[0],
cfg = this.cfg,
autoFillHeight = "autofillheight",
height = "height",
currEl = cfg.getProperty(autoFillHeight),
autoFill = this._autoFillOnHeightChange;
cfg.unsubscribeFromConfigEvent(height, autoFill);
Module.textResizeEvent.unsubscribe(autoFill);
this.changeContentEvent.unsubscribe(autoFill);
if (currEl && fillEl !== currEl && this[currEl]) {
Dom.setStyle(this[currEl], height, "");
}
if (fillEl) {
fillEl = Lang.trim(fillEl.toLowerCase());
cfg.subscribeToConfigEvent(height, autoFill, this[fillEl], this);
Module.textResizeEvent.subscribe(autoFill, this[fillEl], this);
this.changeContentEvent.subscribe(autoFill, this[fillEl], this);
cfg.setProperty(autoFillHeight, fillEl, true);
}
},
/**
* 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.element;
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) {
var zIndex = args[0],
el = this.element;
if (! zIndex) {
zIndex = Dom.getStyle(el, "zIndex");
if (! zIndex || isNaN(zIndex)) {
zIndex = 0;
}
}
if (this.iframe || this.cfg.getProperty("iframe") === true) {
if (zIndex <= 0) {
zIndex = 1;
}
}
Dom.setStyle(el, "zIndex", zIndex);
this.cfg.setProperty("zIndex", zIndex, true);
if (this.iframe) {
this.stackIframe();
}
},
/**
* The default event handler fired when the "xy" property is changed.
* @method configXY
* @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.
*/
configXY: function (type, args, obj) {
var pos = args[0],
x = pos[0],
y = pos[1];
this.cfg.setProperty("x", x);
this.cfg.setProperty("y", y);
this.beforeMoveEvent.fire([x, y]);
x = this.cfg.getProperty("x");
y = this.cfg.getProperty("y");
YAHOO.log(("xy: " + [x, y]), "iframe");
this.cfg.refireEvent("iframe");
this.moveEvent.fire([x, y]);
},
/**
* The default event handler fired when the "x" property is changed.
* @method configX
* @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.
*/
configX: function (type, args, obj) {
var x = args[0],
y = this.cfg.getProperty("y");
this.cfg.setProperty("x", x, true);
this.cfg.setProperty("y", y, true);
this.beforeMoveEvent.fire([x, y]);
x = this.cfg.getProperty("x");
y = this.cfg.getProperty("y");
Dom.setX(this.element, x, true);
this.cfg.setProperty("xy", [x, y], true);
this.cfg.refireEvent("iframe");
this.moveEvent.fire([x, y]);
},
/**
* The default event handler fired when the "y" property is changed.
* @method configY
* @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.
*/
configY: function (type, args, obj) {
var x = this.cfg.getProperty("x"),
y = args[0];
this.cfg.setProperty("x", x, true);
this.cfg.setProperty("y", y, true);
this.beforeMoveEvent.fire([x, y]);
x = this.cfg.getProperty("x");
y = this.cfg.getProperty("y");
Dom.setY(this.element, y, true);
this.cfg.setProperty("xy", [x, y], true);
this.cfg.refireEvent("iframe");
this.moveEvent.fire([x, y]);
},
/**
* Shows the iframe shim, if it has been enabled.
* @method showIframe
*/
showIframe: function () {
var oIFrame = this.iframe,
oParentNode;
if (oIFrame) {
oParentNode = this.element.parentNode;
if (oParentNode != oIFrame.parentNode) {
this._addToParent(oParentNode, oIFrame);
}
oIFrame.style.display = "block";
}
},
/**
* Hides the iframe shim, if it has been enabled.
* @method hideIframe
*/
hideIframe: function () {
if (this.iframe) {
this.iframe.style.display = "none";
}
},
/**
* Syncronizes the size and position of iframe shim to that of its
* corresponding Overlay instance.
* @method syncIframe
*/
syncIframe: function () {
var oIFrame = this.iframe,
oElement = this.element,
nOffset = Overlay.IFRAME_OFFSET,
nDimensionOffset = (nOffset * 2),
aXY;
if (oIFrame) {
// Size <iframe>
oIFrame.style.width = (oElement.offsetWidth + nDimensionOffset + "px");
oIFrame.style.height = (oElement.offsetHeight + nDimensionOffset + "px");
// Position <iframe>
aXY = this.cfg.getProperty("xy");
if (!Lang.isArray(aXY) || (isNaN(aXY[0]) || isNaN(aXY[1]))) {
this.syncPosition();
aXY = this.cfg.getProperty("xy");
}
Dom.setXY(oIFrame, [(aXY[0] - nOffset), (aXY[1] - nOffset)]);
}
},
/**
* Sets the zindex of the iframe shim, if it exists, based on the zindex of
* the Overlay element. The zindex of the iframe is set to be one less
* than the Overlay element's zindex.
*
* <p>NOTE: This method will not bump up the zindex of the Overlay element
* to ensure that the iframe shim has a non-negative zindex.
* If you require the iframe zindex to be 0 or higher, the zindex of
* the Overlay element should be set to a value greater than 0, before
* this method is called.
* </p>
* @method stackIframe
*/
stackIframe: function () {
if (this.iframe) {
var overlayZ = Dom.getStyle(this.element, "zIndex");
if (!YAHOO.lang.isUndefined(overlayZ) && !isNaN(overlayZ)) {
Dom.setStyle(this.iframe, "zIndex", (overlayZ - 1));
}
}
},
/**
* The default event handler fired when the "iframe" property is changed.
* @method configIframe
* @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.
*/
configIframe: function (type, args, obj) {
var bIFrame = args[0];
function createIFrame() {
var oIFrame = this.iframe,
oElement = this.element,
oParent;
if (!oIFrame) {
if (!m_oIFrameTemplate) {
m_oIFrameTemplate = document.createElement("iframe");
if (this.isSecure) {
m_oIFrameTemplate.src = Overlay.IFRAME_SRC;
}
/*
Set the opacity of the <iframe> to 0 so that it
doesn't modify the opacity of any transparent
elements that may be on top of it (like a shadow).
*/
if (UA.ie) {
m_oIFrameTemplate.style.filter = "alpha(opacity=0)";
/*
Need to set the "frameBorder" property to 0
supress the default <iframe> border in IE.
Setting the CSS "border" property alone
doesn't supress it.
*/
m_oIFrameTemplate.frameBorder = 0;
}
else {
m_oIFrameTemplate.style.opacity = "0";
}
m_oIFrameTemplate.style.position = "absolute";
m_oIFrameTemplate.style.border = "none";
m_oIFrameTemplate.style.margin = "0";
m_oIFrameTemplate.style.padding = "0";
m_oIFrameTemplate.style.display = "none";
m_oIFrameTemplate.tabIndex = -1;
m_oIFrameTemplate.className = Overlay.CSS_IFRAME;
}
oIFrame = m_oIFrameTemplate.cloneNode(false);
oIFrame.id = this.id + "_f";
oParent = oElement.parentNode;
var parentNode = oParent || document.body;
this._addToParent(parentNode, oIFrame);
this.iframe = oIFrame;
}
/*
Show the <iframe> before positioning it since the "setXY"
method of DOM requires the element be in the document
and visible.
*/
this.showIframe();
/*
Syncronize the size and position of the <iframe> to that
of the Overlay.
*/
this.syncIframe();
this.stackIframe();
// Add event listeners to update the <iframe> when necessary
if (!this._hasIframeEventListeners) {
this.showEvent.subscribe(this.showIframe);
this.hideEvent.subscribe(this.hideIframe);
this.changeContentEvent.subscribe(this.syncIframe);
this._hasIframeEventListeners = true;
}
}
function onBeforeShow() {
createIFrame.call(this);
this.beforeShowEvent.unsubscribe(onBeforeShow);
this._iframeDeferred = false;
}
if (bIFrame) { // <iframe> shim is enabled
if (this.cfg.getProperty("visible")) {
createIFrame.call(this);
} else {
if (!this._iframeDeferred) {
this.beforeShowEvent.subscribe(onBeforeShow);
this._iframeDeferred = true;
}
}
} else { // <iframe> shim is disabled
this.hideIframe();
if (this._hasIframeEventListeners) {
this.showEvent.unsubscribe(this.showIframe);
this.hideEvent.unsubscribe(this.hideIframe);
this.changeContentEvent.unsubscribe(this.syncIframe);
this._hasIframeEventListeners = false;
}
}
},
/**
* Set's the container's XY value from DOM if not already set.
*
* Differs from syncPosition, in that the XY value is only sync'd with DOM if
* not already set. The method also refire's the XY config property event, so any
* beforeMove, Move event listeners are invoked.
*
* @method _primeXYFromDOM
* @protected
*/
_primeXYFromDOM : function() {
if (YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))) {
// Set CFG XY based on DOM XY
this.syncPosition();
// Account for XY being set silently in syncPosition (no moveTo fired/called)
this.cfg.refireEvent("xy");
this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);
}
},
/**
* The default event handler fired when the "constraintoviewport"
* property is changed.
* @method configConstrainToViewport
* @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.
*/
configConstrainToViewport: function (type, args, obj) {
var val = args[0];
if (val) {
if (! Config.alreadySubscribed(this.beforeMoveEvent, this.enforceConstraints, this)) {
this.beforeMoveEvent.subscribe(this.enforceConstraints, this, true);
}
if (! Config.alreadySubscribed(this.beforeShowEvent, this._primeXYFromDOM)) {
this.beforeShowEvent.subscribe(this._primeXYFromDOM);
}
} else {
this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);
this.beforeMoveEvent.unsubscribe(this.enforceConstraints, this);
}
},
/**
* The default event handler fired when the "context" property
* is changed.
*
* @method configContext
* @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.
*/
configContext: function (type, args, obj) {
var contextArgs = args[0],
contextEl,
elementMagnetCorner,
contextMagnetCorner,
triggers,
offset,
defTriggers = this.CONTEXT_TRIGGERS;
if (contextArgs) {
contextEl = contextArgs[0];
elementMagnetCorner = contextArgs[1];
contextMagnetCorner = contextArgs[2];
triggers = contextArgs[3];
offset = contextArgs[4];
if (defTriggers && defTriggers.length > 0) {
triggers = (triggers || []).concat(defTriggers);
}
if (contextEl) {
if (typeof contextEl == "string") {
this.cfg.setProperty("context", [
document.getElementById(contextEl),
elementMagnetCorner,
contextMagnetCorner,
triggers,
offset],
true);
}
if (elementMagnetCorner && contextMagnetCorner) {
this.align(elementMagnetCorner, contextMagnetCorner, offset);
}
if (this._contextTriggers) {
// Unsubscribe Old Set
this._processTriggers(this._contextTriggers, _UNSUBSCRIBE, this._alignOnTrigger);
}
if (triggers) {
// Subscribe New Set
this._processTriggers(triggers, _SUBSCRIBE, this._alignOnTrigger);
this._contextTriggers = triggers;
}
}
}
},
/**
* Custom Event handler for context alignment triggers. Invokes the align method
*
* @method _alignOnTrigger
* @protected
*
* @param {String} type The event type (not used by the default implementation)
* @param {Any[]} args The array of arguments for the trigger event (not used by the default implementation)
*/
_alignOnTrigger: function(type, args) {
this.align();
},
/**
* Helper method to locate the custom event instance for the event name string
* passed in. As a convenience measure, any custom events passed in are returned.
*
* @method _findTriggerCE
* @private
*
* @param {String|CustomEvent} t Either a CustomEvent, or event type (e.g. "windowScroll") for which a
* custom event instance needs to be looked up from the Overlay._TRIGGER_MAP.
*/
_findTriggerCE : function(t) {
var tce = null;
if (t instanceof CustomEvent) {
tce = t;
} else if (Overlay._TRIGGER_MAP[t]) {
tce = Overlay._TRIGGER_MAP[t];
}
return tce;
},
/**
* Utility method that subscribes or unsubscribes the given
* function from the list of trigger events provided.
*
* @method _processTriggers
* @protected
*
* @param {Array[String|CustomEvent]} triggers An array of either CustomEvents, event type strings
* (e.g. "beforeShow", "windowScroll") to/from which the provided function should be
* subscribed/unsubscribed respectively.
*
* @param {String} mode Either "subscribe" or "unsubscribe", specifying whether or not
* we are subscribing or unsubscribing trigger listeners
*
* @param {Function} fn The function to be subscribed/unsubscribed to/from the trigger event.
* Context is always set to the overlay instance, and no additional object argument
* get passed to the subscribed function.
*/
_processTriggers : function(triggers, mode, fn) {
var t, tce;
for (var i = 0, l = triggers.length; i < l; ++i) {
t = triggers[i];
tce = this._findTriggerCE(t);
if (tce) {
tce[mode](fn, this, true);
} else {
this[mode](t, fn);
}
}
},
// END BUILT-IN PROPERTY EVENT HANDLERS //
/**
* Aligns the Overlay to its context element using the specified corner
* points (represented by the constants TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT,
* and BOTTOM_RIGHT.
* @method align
* @param {String} elementAlign The String representing the corner of
* the Overlay that should be aligned to the context element
* @param {String} contextAlign The corner of the context element
* that the elementAlign corner should stick to.
* @param {Number[]} xyOffset Optional. A 2 element array specifying the x and y pixel offsets which should be applied
* after aligning the element and context corners. For example, passing in [5, -10] for this value, would offset the
* Overlay by 5 pixels along the X axis (horizontally) and -10 pixels along the Y axis (vertically) after aligning the specified corners.
*/
align: function (elementAlign, contextAlign, xyOffset) {
var contextArgs = this.cfg.getProperty("context"),
me = this,
context,
element,
contextRegion;
function doAlign(v, h) {
var alignX = null, alignY = null;
switch (elementAlign) {
case Overlay.TOP_LEFT:
alignX = h;
alignY = v;
break;
case Overlay.TOP_RIGHT:
alignX = h - element.offsetWidth;
alignY = v;
break;
case Overlay.BOTTOM_LEFT:
alignX = h;
alignY = v - element.offsetHeight;
break;
case Overlay.BOTTOM_RIGHT:
alignX = h - element.offsetWidth;
alignY = v - element.offsetHeight;
break;
}
if (alignX !== null && alignY !== null) {
if (xyOffset) {
alignX += xyOffset[0];
alignY += xyOffset[1];
}
me.moveTo(alignX, alignY);
}
}
if (contextArgs) {
context = contextArgs[0];
element = this.element;
me = this;
if (! elementAlign) {
elementAlign = contextArgs[1];
}
if (! contextAlign) {
contextAlign = contextArgs[2];
}
if (!xyOffset && contextArgs[4]) {
xyOffset = contextArgs[4];
}
if (element && context) {
contextRegion = Dom.getRegion(context);
switch (contextAlign) {
case Overlay.TOP_LEFT:
doAlign(contextRegion.top, contextRegion.left);
break;
case Overlay.TOP_RIGHT:
doAlign(contextRegion.top, contextRegion.right);
break;
case Overlay.BOTTOM_LEFT:
doAlign(contextRegion.bottom, contextRegion.left);
break;
case Overlay.BOTTOM_RIGHT:
doAlign(contextRegion.bottom, contextRegion.right);
break;
}
}
}
},
/**
* The default event handler executed when the moveEvent is fired, if the
* "constraintoviewport" is set to true.
* @method enforceConstraints
* @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.
*/
enforceConstraints: function (type, args, obj) {
var pos = args[0];
var cXY = this.getConstrainedXY(pos[0], pos[1]);
this.cfg.setProperty("x", cXY[0], true);
this.cfg.setProperty("y", cXY[1], true);
this.cfg.setProperty("xy", cXY, true);
},
/**
* Shared implementation method for getConstrainedX and getConstrainedY.
*
* <p>
* Given a coordinate value, returns the calculated coordinate required to
* position the Overlay if it is to be constrained to the viewport, based on the
* current element size, viewport dimensions, scroll values and preventoverlap
* settings
* </p>
*
* @method _getConstrainedPos
* @protected
* @param {String} pos The coordinate which needs to be constrained, either "x" or "y"
* @param {Number} The coordinate value which needs to be constrained
* @return {Number} The constrained coordinate value
*/
_getConstrainedPos: function(pos, val) {
var overlayEl = this.element,
buffer = Overlay.VIEWPORT_OFFSET,
x = (pos == "x"),
overlaySize = (x) ? overlayEl.offsetWidth : overlayEl.offsetHeight,
viewportSize = (x) ? Dom.getViewportWidth() : Dom.getViewportHeight(),
docScroll = (x) ? Dom.getDocumentScrollLeft() : Dom.getDocumentScrollTop(),
overlapPositions = (x) ? Overlay.PREVENT_OVERLAP_X : Overlay.PREVENT_OVERLAP_Y,
context = this.cfg.getProperty("context"),
bOverlayFitsInViewport = (overlaySize + buffer < viewportSize),
bPreventContextOverlap = this.cfg.getProperty("preventcontextoverlap") && context && overlapPositions[(context[1] + context[2])],
minConstraint = docScroll + buffer,
maxConstraint = docScroll + viewportSize - overlaySize - buffer,
constrainedVal = val;
if (val < minConstraint || val > maxConstraint) {
if (bPreventContextOverlap) {
constrainedVal = this._preventOverlap(pos, context[0], overlaySize, viewportSize, docScroll);
} else {
if (bOverlayFitsInViewport) {
if (val < minConstraint) {
constrainedVal = minConstraint;
} else if (val > maxConstraint) {
constrainedVal = maxConstraint;
}
} else {
constrainedVal = minConstraint;
}
}
}
return constrainedVal;
},
/**
* Helper method, used to position the Overlap to prevent overlap with the
* context element (used when preventcontextoverlap is enabled)
*
* @method _preventOverlap
* @protected
* @param {String} pos The coordinate to prevent overlap for, either "x" or "y".
* @param {HTMLElement} contextEl The context element
* @param {Number} overlaySize The related overlay dimension value (for "x", the width, for "y", the height)
* @param {Number} viewportSize The related viewport dimension value (for "x", the width, for "y", the height)
* @param {Object} docScroll The related document scroll value (for "x", the scrollLeft, for "y", the scrollTop)
*
* @return {Number} The new coordinate value which was set to prevent overlap
*/
_preventOverlap : function(pos, contextEl, overlaySize, viewportSize, docScroll) {
var x = (pos == "x"),
buffer = Overlay.VIEWPORT_OFFSET,
overlay = this,
contextElPos = ((x) ? Dom.getX(contextEl) : Dom.getY(contextEl)) - docScroll,
contextElSize = (x) ? contextEl.offsetWidth : contextEl.offsetHeight,
minRegionSize = contextElPos - buffer,
maxRegionSize = (viewportSize - (contextElPos + contextElSize)) - buffer,
bFlipped = false,
flip = function () {
var flippedVal;
if ((overlay.cfg.getProperty(pos) - docScroll) > contextElPos) {
flippedVal = (contextElPos - overlaySize);
} else {
flippedVal = (contextElPos + contextElSize);
}
overlay.cfg.setProperty(pos, (flippedVal + docScroll), true);
return flippedVal;
},
setPosition = function () {
var displayRegionSize = ((overlay.cfg.getProperty(pos) - docScroll) > contextElPos) ? maxRegionSize : minRegionSize,
position;
if (overlaySize > displayRegionSize) {
if (bFlipped) {
/*
All possible positions and values have been
tried, but none were successful, so fall back
to the original size and position.
*/
flip();
} else {
flip();
bFlipped = true;
position = setPosition();
}
}
return position;
};
setPosition();
return this.cfg.getProperty(pos);
},
/**
* Given x coordinate value, returns the calculated x coordinate required to
* position the Overlay if it is to be constrained to the viewport, based on the
* current element size, viewport dimensions and scroll values.
*
* @param {Number} x The X coordinate value to be constrained
* @return {Number} The constrained x coordinate
*/
getConstrainedX: function (x) {
return this._getConstrainedPos("x", x);
},
/**
* Given y coordinate value, returns the calculated y coordinate required to
* position the Overlay if it is to be constrained to the viewport, based on the
* current element size, viewport dimensions and scroll values.
*
* @param {Number} y The Y coordinate value to be constrained
* @return {Number} The constrained y coordinate
*/
getConstrainedY : function (y) {
return this._getConstrainedPos("y", y);
},
/**
* Given x, y coordinate values, returns the calculated coordinates required to
* position the Overlay if it is to be constrained to the viewport, based on the
* current element size, viewport dimensions and scroll values.
*
* @param {Number} x The X coordinate value to be constrained
* @param {Number} y The Y coordinate value to be constrained
* @return {Array} The constrained x and y coordinates at index 0 and 1 respectively;
*/
getConstrainedXY: function(x, y) {
return [this.getConstrainedX(x), this.getConstrainedY(y)];
},
/**
* Centers the container in the viewport.
* @method center
*/
center: function () {
var nViewportOffset = Overlay.VIEWPORT_OFFSET,
elementWidth = this.element.offsetWidth,
elementHeight = this.element.offsetHeight,
viewPortWidth = Dom.getViewportWidth(),
viewPortHeight = Dom.getViewportHeight(),
x,
y;
if (elementWidth < viewPortWidth) {
x = (viewPortWidth / 2) - (elementWidth / 2) + Dom.getDocumentScrollLeft();
} else {
x = nViewportOffset + Dom.getDocumentScrollLeft();
}
if (elementHeight < viewPortHeight) {
y = (viewPortHeight / 2) - (elementHeight / 2) + Dom.getDocumentScrollTop();
} else {
y = nViewportOffset + Dom.getDocumentScrollTop();
}
this.cfg.setProperty("xy", [parseInt(x, 10), parseInt(y, 10)]);
this.cfg.refireEvent("iframe");
if (UA.webkit) {
this.forceContainerRedraw();
}
},
/**
* Synchronizes the Panel's "xy", "x", and "y" properties with the
* Panel's position in the DOM. This is primarily used to update
* position information during drag & drop.
* @method syncPosition
*/
syncPosition: function () {
var pos = Dom.getXY(this.element);
this.cfg.setProperty("x", pos[0], true);
this.cfg.setProperty("y", pos[1], true);
this.cfg.setProperty("xy", pos, true);
},
/**
* 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
*/
onDomResize: function (e, obj) {
var me = this;
Overlay.superclass.onDomResize.call(this, e, obj);
setTimeout(function () {
me.syncPosition();
me.cfg.refireEvent("iframe");
me.cfg.refireEvent("context");
}, 0);
},
/**
* Determines the content box height of the given element (height of the element, without padding or borders) in pixels.
*
* @method _getComputedHeight
* @private
* @param {HTMLElement} el The element for which the content height needs to be determined
* @return {Number} The content box height of the given element, or null if it could not be determined.
*/
_getComputedHeight : (function() {
if (document.defaultView && document.defaultView.getComputedStyle) {
return function(el) {
var height = null;
if (el.ownerDocument && el.ownerDocument.defaultView) {
var computed = el.ownerDocument.defaultView.getComputedStyle(el, '');
if (computed) {
height = parseInt(computed.height, 10);
}
}
return (Lang.isNumber(height)) ? height : null;
};
} else {
return function(el) {
var height = null;
if (el.style.pixelHeight) {
height = el.style.pixelHeight;
}
return (Lang.isNumber(height)) ? height : null;
};
}
})(),
/**
* autofillheight validator. Verifies that the autofill value is either null
* or one of the strings : "body", "header" or "footer".
*
* @method _validateAutoFillHeight
* @protected
* @param {String} val
* @return true, if valid, false otherwise
*/
_validateAutoFillHeight : function(val) {
return (!val) || (Lang.isString(val) && Overlay.STD_MOD_RE.test(val));
},
/**
* The default custom event handler executed when the overlay'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) {
var height = this.cfg.getProperty("height");
if ((height && height !== "auto") || (height === 0)) {
this.fillHeight(el);
}
},
/**
* Returns the sub-pixel height of the el, using getBoundingClientRect, if available,
* otherwise returns the offsetHeight
* @method _getPreciseHeight
* @private
* @param {HTMLElement} el
* @return {Float} The sub-pixel height if supported by the browser, else the rounded height.
*/
_getPreciseHeight : function(el) {
var height = el.offsetHeight;
if (el.getBoundingClientRect) {
var rect = el.getBoundingClientRect();
height = rect.bottom - rect.top;
}
return height;
},
/**
* <p>
* Sets the height on the provided header, body or footer element to
* fill out the height of the container. It determines the height of the
* containers content box, based on it's configured height value, and
* sets the height of the autofillheight element to fill out any
* space remaining after the other standard module element heights
* have been accounted for.
* </p>
* <p><strong>NOTE:</strong> This method is not designed to work if an explicit
* height has not been set on the container, since for an "auto" height container,
* the heights of the header/body/footer will drive the height of the container.</p>
*
* @method fillHeight
* @param {HTMLElement} el The element which should be resized to fill out the height
* of the container element.
*/
fillHeight : function(el) {
if (el) {
var container = this.innerElement || this.element,
containerEls = [this.header, this.body, this.footer],
containerEl,
total = 0,
filled = 0,
remaining = 0,
validEl = false;
for (var i = 0, l = containerEls.length; i < l; i++) {
containerEl = containerEls[i];
if (containerEl) {
if (el !== containerEl) {
filled += this._getPreciseHeight(containerEl);
} else {
validEl = true;
}
}
}
if (validEl) {
if (UA.ie || UA.opera) {
// Need to set height to 0, to allow height to be reduced
Dom.setStyle(el, 'height', 0 + 'px');
}
total = this._getComputedHeight(container);
// Fallback, if we can't get computed value for content height
if (total === null) {
Dom.addClass(container, "yui-override-padding");
total = container.clientHeight; // Content, No Border, 0 Padding (set by yui-override-padding)
Dom.removeClass(container, "yui-override-padding");
}
remaining = Math.max(total - filled, 0);
Dom.setStyle(el, "height", remaining + "px");
// Re-adjust height if required, to account for el padding and border
if (el.offsetHeight != remaining) {
remaining = Math.max(remaining - (el.offsetHeight - remaining), 0);
}
Dom.setStyle(el, "height", remaining + "px");
}
}
},
/**
* Places the Overlay on top of all other instances of
* YAHOO.widget.Overlay.
* @method bringToTop
*/
bringToTop: function () {
var aOverlays = [],
oElement = this.element;
function compareZIndexDesc(p_oOverlay1, p_oOverlay2) {
var sZIndex1 = Dom.getStyle(p_oOverlay1, "zIndex"),
sZIndex2 = Dom.getStyle(p_oOverlay2, "zIndex"),
nZIndex1 = (!sZIndex1 || isNaN(sZIndex1)) ? 0 : parseInt(sZIndex1, 10),
nZIndex2 = (!sZIndex2 || isNaN(sZIndex2)) ? 0 : parseInt(sZIndex2, 10);
if (nZIndex1 > nZIndex2) {
return -1;
} else if (nZIndex1 < nZIndex2) {
return 1;
} else {
return 0;
}
}
function isOverlayElement(p_oElement) {
var isOverlay = Dom.hasClass(p_oElement, Overlay.CSS_OVERLAY),
Panel = YAHOO.widget.Panel;
if (isOverlay && !Dom.isAncestor(oElement, p_oElement)) {
if (Panel && Dom.hasClass(p_oElement, Panel.CSS_PANEL)) {
aOverlays[aOverlays.length] = p_oElement.parentNode;
} else {
aOverlays[aOverlays.length] = p_oElement;
}
}
}
Dom.getElementsBy(isOverlayElement, "DIV", document.body);
aOverlays.sort(compareZIndexDesc);
var oTopOverlay = aOverlays[0],
nTopZIndex;
if (oTopOverlay) {
nTopZIndex = Dom.getStyle(oTopOverlay, "zIndex");
if (!isNaN(nTopZIndex)) {
var bRequiresBump = false;
if (oTopOverlay != oElement) {
bRequiresBump = true;
} else if (aOverlays.length > 1) {
var nNextZIndex = Dom.getStyle(aOverlays[1], "zIndex");
// Don't rely on DOM order to stack if 2 overlays are at the same zindex.
if (!isNaN(nNextZIndex) && (nTopZIndex == nNextZIndex)) {
bRequiresBump = true;
}
}
if (bRequiresBump) {
this.cfg.setProperty("zindex", (parseInt(nTopZIndex, 10) + 2));
}
}
}
},
/**
* Removes the Overlay element from the DOM and sets all child
* elements to null.
* @method destroy
*/
destroy: function () {
if (this.iframe) {
this.iframe.parentNode.removeChild(this.iframe);
}
this.iframe = null;
Overlay.windowResizeEvent.unsubscribe(
this.doCenterOnDOMEvent, this);
Overlay.windowScrollEvent.unsubscribe(
this.doCenterOnDOMEvent, this);
Module.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);
if (this._contextTriggers) {
// Unsubscribe context triggers - to cover context triggers which listen for global
// events such as windowResize and windowScroll. Easier just to unsubscribe all
this._processTriggers(this._contextTriggers, _UNSUBSCRIBE, this._alignOnTrigger);
}
Overlay.superclass.destroy.call(this);
},
/**
* Can be used to force the container to repaint/redraw it's contents.
* <p>
* By default applies and then removes a 1px bottom margin through the
* application/removal of a "yui-force-redraw" class.
* </p>
* <p>
* It is currently used by Overlay to force a repaint for webkit
* browsers, when centering.
* </p>
* @method forceContainerRedraw
*/
forceContainerRedraw : function() {
var c = this;
Dom.addClass(c.element, "yui-force-redraw");
setTimeout(function() {
Dom.removeClass(c.element, "yui-force-redraw");
}, 0);
},
/**
* Returns a String representation of the object.
* @method toString
* @return {String} The string representation of the Overlay.
*/
toString: function () {
return "Overlay " + this.id;
}
});
}());
(function () {
/**
* OverlayManager is used for maintaining the focus status of
* multiple Overlays.
* @namespace YAHOO.widget
* @namespace YAHOO.widget
* @class OverlayManager
* @constructor
* @param {Array} overlays Optional. A collection of Overlays to register
* with the manager.
* @param {Object} userConfig The object literal representing the user
* configuration of the OverlayManager
*/
YAHOO.widget.OverlayManager = function (userConfig) {
this.init(userConfig);
};
var Overlay = YAHOO.widget.Overlay,
Event = YAHOO.util.Event,
Dom = YAHOO.util.Dom,
Config = YAHOO.util.Config,
CustomEvent = YAHOO.util.CustomEvent,
OverlayManager = YAHOO.widget.OverlayManager;
/**
* The CSS class representing a focused Overlay
* @property OverlayManager.CSS_FOCUSED
* @static
* @final
* @type String
*/
OverlayManager.CSS_FOCUSED = "focused";
OverlayManager.prototype = {
/**
* The class's constructor function
* @property contructor
* @type Function
*/
constructor: OverlayManager,
/**
* The array of Overlays that are currently registered
* @property overlays
* @type YAHOO.widget.Overlay[]
*/
overlays: null,
/**
* Initializes the default configuration of the OverlayManager
* @method initDefaultConfig
*/
initDefaultConfig: function () {
/**
* The collection of registered Overlays in use by
* the OverlayManager
* @config overlays
* @type YAHOO.widget.Overlay[]
* @default null
*/
this.cfg.addProperty("overlays", { suppressEvent: true } );
/**
* The default DOM event that should be used to focus an Overlay
* @config focusevent
* @type String
* @default "mousedown"
*/
this.cfg.addProperty("focusevent", { value: "mousedown" } );
},
/**
* Initializes the OverlayManager
* @method init
* @param {Overlay[]} overlays Optional. A collection of Overlays to
* register with the manager.
* @param {Object} userConfig The object literal representing the user
* configuration of the OverlayManager
*/
init: function (userConfig) {
/**
* The OverlayManager's Config object used for monitoring
* configuration properties.
* @property cfg
* @type Config
*/
this.cfg = new Config(this);
this.initDefaultConfig();
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
this.cfg.fireQueue();
/**
* The currently activated Overlay
* @property activeOverlay
* @private
* @type YAHOO.widget.Overlay
*/
var activeOverlay = null;
/**
* Returns the currently focused Overlay
* @method getActive
* @return {Overlay} The currently focused Overlay
*/
this.getActive = function () {
return activeOverlay;
};
/**
* Focuses the specified Overlay
* @method focus
* @param {Overlay} overlay The Overlay to focus
* @param {String} overlay The id of the Overlay to focus
*/
this.focus = function (overlay) {
var o = this.find(overlay);
if (o) {
o.focus();
}
};
/**
* Removes the specified Overlay from the manager
* @method remove
* @param {Overlay} overlay The Overlay to remove
* @param {String} overlay The id of the Overlay to remove
*/
this.remove = function (overlay) {
var o = this.find(overlay),
originalZ;
if (o) {
if (activeOverlay == o) {
activeOverlay = null;
}
var bDestroyed = (o.element === null && o.cfg === null) ? true : false;
if (!bDestroyed) {
// Set it's zindex so that it's sorted to the end.
originalZ = Dom.getStyle(o.element, "zIndex");
o.cfg.setProperty("zIndex", -1000, true);
}
this.overlays.sort(this.compareZIndexDesc);
this.overlays = this.overlays.slice(0, (this.overlays.length - 1));
o.hideEvent.unsubscribe(o.blur);
o.destroyEvent.unsubscribe(this._onOverlayDestroy, o);
o.focusEvent.unsubscribe(this._onOverlayFocusHandler, o);
o.blurEvent.unsubscribe(this._onOverlayBlurHandler, o);
if (!bDestroyed) {
Event.removeListener(o.element, this.cfg.getProperty("focusevent"), this._onOverlayElementFocus);
o.cfg.setProperty("zIndex", originalZ, true);
o.cfg.setProperty("manager", null);
}
/* _managed Flag for custom or existing. Don't want to remove existing */
if (o.focusEvent._managed) { o.focusEvent = null; }
if (o.blurEvent._managed) { o.blurEvent = null; }
if (o.focus._managed) { o.focus = null; }
if (o.blur._managed) { o.blur = null; }
}
};
/**
* Removes focus from all registered Overlays in the manager
* @method blurAll
*/
this.blurAll = function () {
var nOverlays = this.overlays.length,
i;
if (nOverlays > 0) {
i = nOverlays - 1;
do {
this.overlays[i].blur();
}
while(i--);
}
};
/**
* Updates the state of the OverlayManager and overlay, as a result of the overlay
* being blurred.
*
* @method _manageBlur
* @param {Overlay} overlay The overlay instance which got blurred.
* @protected
*/
this._manageBlur = function (overlay) {
var changed = false;
if (activeOverlay == overlay) {
Dom.removeClass(activeOverlay.element, OverlayManager.CSS_FOCUSED);
activeOverlay = null;
changed = true;
}
return changed;
};
/**
* Updates the state of the OverlayManager and overlay, as a result of the overlay
* receiving focus.
*
* @method _manageFocus
* @param {Overlay} overlay The overlay instance which got focus.
* @protected
*/
this._manageFocus = function(overlay) {
var changed = false;
if (activeOverlay != overlay) {
if (activeOverlay) {
activeOverlay.blur();
}
activeOverlay = overlay;
this.bringToTop(activeOverlay);
Dom.addClass(activeOverlay.element, OverlayManager.CSS_FOCUSED);
changed = true;
}
return changed;
};
var overlays = this.cfg.getProperty("overlays");
if (! this.overlays) {
this.overlays = [];
}
if (overlays) {
this.register(overlays);
this.overlays.sort(this.compareZIndexDesc);
}
},
/**
* @method _onOverlayElementFocus
* @description Event handler for the DOM event that is used to focus
* the Overlay instance as specified by the "focusevent"
* configuration property.
* @private
* @param {Event} p_oEvent Object representing the DOM event
* object passed back by the event utility (Event).
*/
_onOverlayElementFocus: function (p_oEvent) {
var oTarget = Event.getTarget(p_oEvent),
oClose = this.close;
if (oClose && (oTarget == oClose || Dom.isAncestor(oClose, oTarget))) {
this.blur();
} else {
this.focus();
}
},
/**
* @method _onOverlayDestroy
* @description "destroy" event handler for the Overlay.
* @private
* @param {String} p_sType String representing the name of the event
* that was fired.
* @param {Array} p_aArgs Array of arguments sent when the event
* was fired.
* @param {Overlay} p_oOverlay Object representing the overlay that
* fired the event.
*/
_onOverlayDestroy: function (p_sType, p_aArgs, p_oOverlay) {
this.remove(p_oOverlay);
},
/**
* @method _onOverlayFocusHandler
*
* @description focusEvent Handler, used to delegate to _manageFocus with the correct arguments.
*
* @private
* @param {String} p_sType String representing the name of the event
* that was fired.
* @param {Array} p_aArgs Array of arguments sent when the event
* was fired.
* @param {Overlay} p_oOverlay Object representing the overlay that
* fired the event.
*/
_onOverlayFocusHandler: function(p_sType, p_aArgs, p_oOverlay) {
this._manageFocus(p_oOverlay);
},
/**
* @method _onOverlayBlurHandler
* @description blurEvent Handler, used to delegate to _manageBlur with the correct arguments.
*
* @private
* @param {String} p_sType String representing the name of the event
* that was fired.
* @param {Array} p_aArgs Array of arguments sent when the event
* was fired.
* @param {Overlay} p_oOverlay Object representing the overlay that
* fired the event.
*/
_onOverlayBlurHandler: function(p_sType, p_aArgs, p_oOverlay) {
this._manageBlur(p_oOverlay);
},
/**
* Subscribes to the Overlay based instance focusEvent, to allow the OverlayManager to
* monitor focus state.
*
* If the instance already has a focusEvent (e.g. Menu), OverlayManager will subscribe
* to the existing focusEvent, however if a focusEvent or focus method does not exist
* on the instance, the _bindFocus method will add them, and the focus method will
* update the OverlayManager's state directly.
*
* @method _bindFocus
* @param {Overlay} overlay The overlay for which focus needs to be managed
* @protected
*/
_bindFocus : function(overlay) {
var mgr = this;
if (!overlay.focusEvent) {
overlay.focusEvent = overlay.createEvent("focus");
overlay.focusEvent.signature = CustomEvent.LIST;
overlay.focusEvent._managed = true;
} else {
overlay.focusEvent.subscribe(mgr._onOverlayFocusHandler, overlay, mgr);
}
if (!overlay.focus) {
Event.on(overlay.element, mgr.cfg.getProperty("focusevent"), mgr._onOverlayElementFocus, null, overlay);
overlay.focus = function () {
if (mgr._manageFocus(this)) {
// For Panel/Dialog
if (this.cfg.getProperty("visible") && this.focusFirst) {
this.focusFirst();
}
this.focusEvent.fire();
}
};
overlay.focus._managed = true;
}
},
/**
* Subscribes to the Overlay based instance's blurEvent to allow the OverlayManager to
* monitor blur state.
*
* If the instance already has a blurEvent (e.g. Menu), OverlayManager will subscribe
* to the existing blurEvent, however if a blurEvent or blur method does not exist
* on the instance, the _bindBlur method will add them, and the blur method
* update the OverlayManager's state directly.
*
* @method _bindBlur
* @param {Overlay} overlay The overlay for which blur needs to be managed
* @protected
*/
_bindBlur : function(overlay) {
var mgr = this;
if (!overlay.blurEvent) {
overlay.blurEvent = overlay.createEvent("blur");
overlay.blurEvent.signature = CustomEvent.LIST;
overlay.focusEvent._managed = true;
} else {
overlay.blurEvent.subscribe(mgr._onOverlayBlurHandler, overlay, mgr);
}
if (!overlay.blur) {
overlay.blur = function () {
if (mgr._manageBlur(this)) {
this.blurEvent.fire();
}
};
overlay.blur._managed = true;
}
overlay.hideEvent.subscribe(overlay.blur);
},
/**
* Subscribes to the Overlay based instance's destroyEvent, to allow the Overlay
* to be removed for the OverlayManager when destroyed.
*
* @method _bindDestroy
* @param {Overlay} overlay The overlay instance being managed
* @protected
*/
_bindDestroy : function(overlay) {
var mgr = this;
overlay.destroyEvent.subscribe(mgr._onOverlayDestroy, overlay, mgr);
},
/**
* Ensures the zIndex configuration property on the managed overlay based instance
* is set to the computed zIndex value from the DOM (with "auto" translating to 0).
*
* @method _syncZIndex
* @param {Overlay} overlay The overlay instance being managed
* @protected
*/
_syncZIndex : function(overlay) {
var zIndex = Dom.getStyle(overlay.element, "zIndex");
if (!isNaN(zIndex)) {
overlay.cfg.setProperty("zIndex", parseInt(zIndex, 10));
} else {
overlay.cfg.setProperty("zIndex", 0);
}
},
/**
* Registers an Overlay or an array of Overlays with the manager. Upon
* registration, the Overlay receives functions for focus and blur,
* along with CustomEvents for each.
*
* @method register
* @param {Overlay} overlay An Overlay to register with the manager.
* @param {Overlay[]} overlay An array of Overlays to register with
* the manager.
* @return {boolean} true if any Overlays are registered.
*/
register: function (overlay) {
var registered = false,
i,
n;
if (overlay instanceof Overlay) {
overlay.cfg.addProperty("manager", { value: this } );
this._bindFocus(overlay);
this._bindBlur(overlay);
this._bindDestroy(overlay);
this._syncZIndex(overlay);
this.overlays.push(overlay);
this.bringToTop(overlay);
registered = true;
} else if (overlay instanceof Array) {
for (i = 0, n = overlay.length; i < n; i++) {
registered = this.register(overlay[i]) || registered;
}
}
return registered;
},
/**
* Places the specified Overlay instance on top of all other
* Overlay instances.
* @method bringToTop
* @param {YAHOO.widget.Overlay} p_oOverlay Object representing an
* Overlay instance.
* @param {String} p_oOverlay String representing the id of an
* Overlay instance.
*/
bringToTop: function (p_oOverlay) {
var oOverlay = this.find(p_oOverlay),
nTopZIndex,
oTopOverlay,
aOverlays;
if (oOverlay) {
aOverlays = this.overlays;
aOverlays.sort(this.compareZIndexDesc);
oTopOverlay = aOverlays[0];
if (oTopOverlay) {
nTopZIndex = Dom.getStyle(oTopOverlay.element, "zIndex");
if (!isNaN(nTopZIndex)) {
var bRequiresBump = false;
if (oTopOverlay !== oOverlay) {
bRequiresBump = true;
} else if (aOverlays.length > 1) {
var nNextZIndex = Dom.getStyle(aOverlays[1].element, "zIndex");
// Don't rely on DOM order to stack if 2 overlays are at the same zindex.
if (!isNaN(nNextZIndex) && (nTopZIndex == nNextZIndex)) {
bRequiresBump = true;
}
}
if (bRequiresBump) {
oOverlay.cfg.setProperty("zindex", (parseInt(nTopZIndex, 10) + 2));
}
}
aOverlays.sort(this.compareZIndexDesc);
}
}
},
/**
* Attempts to locate an Overlay by instance or ID.
* @method find
* @param {Overlay} overlay An Overlay to locate within the manager
* @param {String} overlay An Overlay id to locate within the manager
* @return {Overlay} The requested Overlay, if found, or null if it
* cannot be located.
*/
find: function (overlay) {
var isInstance = overlay instanceof Overlay,
overlays = this.overlays,
n = overlays.length,
found = null,
o,
i;
if (isInstance || typeof overlay == "string") {
for (i = n-1; i >= 0; i--) {
o = overlays[i];
if ((isInstance && (o === overlay)) || (o.id == overlay)) {
found = o;
break;
}
}
}
return found;
},
/**
* Used for sorting the manager's Overlays by z-index.
* @method compareZIndexDesc
* @private
* @return {Number} 0, 1, or -1, depending on where the Overlay should
* fall in the stacking order.
*/
compareZIndexDesc: function (o1, o2) {
var zIndex1 = (o1.cfg) ? o1.cfg.getProperty("zIndex") : null, // Sort invalid (destroyed)
zIndex2 = (o2.cfg) ? o2.cfg.getProperty("zIndex") : null; // objects at bottom.
if (zIndex1 === null && zIndex2 === null) {
return 0;
} else if (zIndex1 === null){
return 1;
} else if (zIndex2 === null) {
return -1;
} else if (zIndex1 > zIndex2) {
return -1;
} else if (zIndex1 < zIndex2) {
return 1;
} else {
return 0;
}
},
/**
* Shows all Overlays in the manager.
* @method showAll
*/
showAll: function () {
var overlays = this.overlays,
n = overlays.length,
i;
for (i = n - 1; i >= 0; i--) {
overlays[i].show();
}
},
/**
* Hides all Overlays in the manager.
* @method hideAll
*/
hideAll: function () {
var overlays = this.overlays,
n = overlays.length,
i;
for (i = n - 1; i >= 0; i--) {
overlays[i].hide();
}
},
/**
* Returns a string representation of the object.
* @method toString
* @return {String} The string representation of the OverlayManager
*/
toString: function () {
return "OverlayManager";
}
};
}());
(function () {
/**
* Tooltip is an implementation of Overlay that behaves like an OS tooltip,
* displaying when the user mouses over a particular element, and
* disappearing on mouse out.
* @namespace YAHOO.widget
* @class Tooltip
* @extends YAHOO.widget.Overlay
* @constructor
* @param {String} el The element ID representing the Tooltip <em>OR</em>
* @param {HTMLElement} el The element representing the Tooltip
* @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.Tooltip = function (el, userConfig) {
YAHOO.widget.Tooltip.superclass.constructor.call(this, el, userConfig);
};
var Lang = YAHOO.lang,
Event = YAHOO.util.Event,
CustomEvent = YAHOO.util.CustomEvent,
Dom = YAHOO.util.Dom,
Tooltip = YAHOO.widget.Tooltip,
UA = YAHOO.env.ua,
bIEQuirks = (UA.ie && (UA.ie <= 6 || document.compatMode == "BackCompat")),
m_oShadowTemplate,
/**
* Constant representing the Tooltip's configuration properties
* @property DEFAULT_CONFIG
* @private
* @final
* @type Object
*/
DEFAULT_CONFIG = {
"PREVENT_OVERLAP": {
key: "preventoverlap",
value: true,
validator: Lang.isBoolean,
supercedes: ["x", "y", "xy"]
},
"SHOW_DELAY": {
key: "showdelay",
value: 200,
validator: Lang.isNumber
},
"AUTO_DISMISS_DELAY": {
key: "autodismissdelay",
value: 5000,
validator: Lang.isNumber
},
"HIDE_DELAY": {
key: "hidedelay",
value: 250,
validator: Lang.isNumber
},
"TEXT": {
key: "text",
suppressEvent: true
},
"CONTAINER": {
key: "container"
},
"DISABLED": {
key: "disabled",
value: false,
suppressEvent: true
},
"XY_OFFSET": {
key: "xyoffset",
value: [0, 25],
suppressEvent: true
}
},
/**
* Constant representing the name of the Tooltip's events
* @property EVENT_TYPES
* @private
* @final
* @type Object
*/
EVENT_TYPES = {
"CONTEXT_MOUSE_OVER": "contextMouseOver",
"CONTEXT_MOUSE_OUT": "contextMouseOut",
"CONTEXT_TRIGGER": "contextTrigger"
};
/**
* Constant representing the Tooltip CSS class
* @property YAHOO.widget.Tooltip.CSS_TOOLTIP
* @static
* @final
* @type String
*/
Tooltip.CSS_TOOLTIP = "yui-tt";
function restoreOriginalWidth(sOriginalWidth, sForcedWidth) {
var oConfig = this.cfg,
sCurrentWidth = oConfig.getProperty("width");
if (sCurrentWidth == sForcedWidth) {
oConfig.setProperty("width", sOriginalWidth);
}
}
/*
changeContent event handler that sets a Tooltip instance's "width"
configuration property to the value of its root HTML
elements's offsetWidth if a specific width has not been set.
*/
function setWidthToOffsetWidth(p_sType, p_aArgs) {
if ("_originalWidth" in this) {
restoreOriginalWidth.call(this, this._originalWidth, this._forcedWidth);
}
var oBody = document.body,
oConfig = this.cfg,
sOriginalWidth = oConfig.getProperty("width"),
sNewWidth,
oClone;
if ((!sOriginalWidth || sOriginalWidth == "auto") &&
(oConfig.getProperty("container") != oBody ||
oConfig.getProperty("x") >= Dom.getViewportWidth() ||
oConfig.getProperty("y") >= Dom.getViewportHeight())) {
oClone = this.element.cloneNode(true);
oClone.style.visibility = "hidden";
oClone.style.top = "0px";
oClone.style.left = "0px";
oBody.appendChild(oClone);
sNewWidth = (oClone.offsetWidth + "px");
oBody.removeChild(oClone);
oClone = null;
oConfig.setProperty("width", sNewWidth);
oConfig.refireEvent("xy");
this._originalWidth = sOriginalWidth || "";
this._forcedWidth = sNewWidth;
}
}
// "onDOMReady" that renders the ToolTip
function onDOMReady(p_sType, p_aArgs, p_oObject) {
this.render(p_oObject);
}
// "init" event handler that automatically renders the Tooltip
function onInit() {
Event.onDOMReady(onDOMReady, this.cfg.getProperty("container"), this);
}
YAHOO.extend(Tooltip, YAHOO.widget.Overlay, {
/**
* The Tooltip initialization method. This method is automatically
* called by the constructor. A Tooltip is automatically rendered by
* the init method, and it also is set to be invisible by default,
* and constrained to viewport by default as well.
* @method init
* @param {String} el The element ID representing the Tooltip <em>OR</em>
* @param {HTMLElement} el The element representing the Tooltip
* @param {Object} userConfig The configuration object literal
* containing the configuration that should be set for this Tooltip.
* See configuration documentation for more details.
*/
init: function (el, userConfig) {
this.logger = new YAHOO.widget.LogWriter(this.toString());
Tooltip.superclass.init.call(this, el);
this.beforeInitEvent.fire(Tooltip);
Dom.addClass(this.element, Tooltip.CSS_TOOLTIP);
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
this.cfg.queueProperty("visible", false);
this.cfg.queueProperty("constraintoviewport", true);
this.setBody("");
this.subscribe("changeContent", setWidthToOffsetWidth);
this.subscribe("init", onInit);
this.subscribe("render", this.onRender);
this.initEvent.fire(Tooltip);
},
/**
* Initializes the custom events for Tooltip
* @method initEvents
*/
initEvents: function () {
Tooltip.superclass.initEvents.call(this);
var SIGNATURE = CustomEvent.LIST;
/**
* CustomEvent fired when user mouses over a context element. Returning false from
* a subscriber to this event will prevent the tooltip from being displayed for
* the current context element.
*
* @event contextMouseOverEvent
* @param {HTMLElement} context The context element which the user just moused over
* @param {DOMEvent} e The DOM event object, associated with the mouse over
*/
this.contextMouseOverEvent = this.createEvent(EVENT_TYPES.CONTEXT_MOUSE_OVER);
this.contextMouseOverEvent.signature = SIGNATURE;
/**
* CustomEvent fired when the user mouses out of a context element.
*
* @event contextMouseOutEvent
* @param {HTMLElement} context The context element which the user just moused out of
* @param {DOMEvent} e The DOM event object, associated with the mouse out
*/
this.contextMouseOutEvent = this.createEvent(EVENT_TYPES.CONTEXT_MOUSE_OUT);
this.contextMouseOutEvent.signature = SIGNATURE;
/**
* CustomEvent fired just before the tooltip is displayed for the current context.
* <p>
* You can subscribe to this event if you need to set up the text for the
* tooltip based on the context element for which it is about to be displayed.
* </p>
* <p>This event differs from the beforeShow event in following respects:</p>
* <ol>
* <li>
* When moving from one context element to another, if the tooltip is not
* hidden (the <code>hidedelay</code> is not reached), the beforeShow and Show events will not
* be fired when the tooltip is displayed for the new context since it is already visible.
* However the contextTrigger event is always fired before displaying the tooltip for
* a new context.
* </li>
* <li>
* The trigger event provides access to the context element, allowing you to
* set the text of the tooltip based on context element for which the tooltip is
* triggered.
* </li>
* </ol>
* <p>
* It is not possible to prevent the tooltip from being displayed
* using this event. You can use the contextMouseOverEvent if you need to prevent
* the tooltip from being displayed.
* </p>
* @event contextTriggerEvent
* @param {HTMLElement} context The context element for which the tooltip is triggered
*/
this.contextTriggerEvent = this.createEvent(EVENT_TYPES.CONTEXT_TRIGGER);
this.contextTriggerEvent.signature = SIGNATURE;
},
/**
* Initializes the class's configurable properties which can be
* changed using the Overlay's Config object (cfg).
* @method initDefaultConfig
*/
initDefaultConfig: function () {
Tooltip.superclass.initDefaultConfig.call(this);
/**
* Specifies whether the Tooltip should be kept from overlapping
* its context element.
* @config preventoverlap
* @type Boolean
* @default true
*/
this.cfg.addProperty(DEFAULT_CONFIG.PREVENT_OVERLAP.key, {
value: DEFAULT_CONFIG.PREVENT_OVERLAP.value,
validator: DEFAULT_CONFIG.PREVENT_OVERLAP.validator,
supercedes: DEFAULT_CONFIG.PREVENT_OVERLAP.supercedes
});
/**
* The number of milliseconds to wait before showing a Tooltip
* on mouseover.
* @config showdelay
* @type Number
* @default 200
*/
this.cfg.addProperty(DEFAULT_CONFIG.SHOW_DELAY.key, {
handler: this.configShowDelay,
value: 200,
validator: DEFAULT_CONFIG.SHOW_DELAY.validator
});
/**
* The number of milliseconds to wait before automatically
* dismissing a Tooltip after the mouse has been resting on the
* context element.
* @config autodismissdelay
* @type Number
* @default 5000
*/
this.cfg.addProperty(DEFAULT_CONFIG.AUTO_DISMISS_DELAY.key, {
handler: this.configAutoDismissDelay,
value: DEFAULT_CONFIG.AUTO_DISMISS_DELAY.value,
validator: DEFAULT_CONFIG.AUTO_DISMISS_DELAY.validator
});
/**
* The number of milliseconds to wait before hiding a Tooltip
* after mouseout.
* @config hidedelay
* @type Number
* @default 250
*/
this.cfg.addProperty(DEFAULT_CONFIG.HIDE_DELAY.key, {
handler: this.configHideDelay,
value: DEFAULT_CONFIG.HIDE_DELAY.value,
validator: DEFAULT_CONFIG.HIDE_DELAY.validator
});
/**
* Specifies the Tooltip's text.
* @config text
* @type String
* @default null
*/
this.cfg.addProperty(DEFAULT_CONFIG.TEXT.key, {
handler: this.configText,
suppressEvent: DEFAULT_CONFIG.TEXT.suppressEvent
});
/**
* Specifies the container element that the Tooltip's markup
* should be rendered into.
* @config container
* @type HTMLElement/String
* @default document.body
*/
this.cfg.addProperty(DEFAULT_CONFIG.CONTAINER.key, {
handler: this.configContainer,
value: document.body
});
/**
* Specifies whether or not the tooltip is disabled. Disabled tooltips
* will not be displayed. If the tooltip is driven by the title attribute
* of the context element, the title attribute will still be removed for
* disabled tooltips, to prevent default tooltip behavior.
*
* @config disabled
* @type Boolean
* @default false
*/
this.cfg.addProperty(DEFAULT_CONFIG.DISABLED.key, {
handler: this.configContainer,
value: DEFAULT_CONFIG.DISABLED.value,
supressEvent: DEFAULT_CONFIG.DISABLED.suppressEvent
});
/**
* Specifies the XY offset from the mouse position, where the tooltip should be displayed, specified
* as a 2 element array (e.g. [10, 20]);
*
* @config xyoffset
* @type Array
* @default [0, 25]
*/
this.cfg.addProperty(DEFAULT_CONFIG.XY_OFFSET.key, {
value: DEFAULT_CONFIG.XY_OFFSET.value.concat(),
supressEvent: DEFAULT_CONFIG.XY_OFFSET.suppressEvent
});
/**
* Specifies the element or elements that the Tooltip should be
* anchored to on mouseover.
* @config context
* @type HTMLElement[]/String[]
* @default null
*/
/**
* String representing the width of the Tooltip. <em>Please note:
* </em> As of version 2.3 if either no value or a value of "auto"
* is specified, and the Toolip's "container" configuration property
* is set to something other than <code>document.body</code> or
* its "context" element resides outside the immediately visible
* portion of the document, the width of the Tooltip will be
* calculated based on the offsetWidth of its root HTML and set just
* before it is made visible. The original value will be
* restored when the Tooltip is hidden. This ensures the Tooltip is
* rendered at a usable width. For more information see
* YUILibrary bug #1685496 and YUILibrary
* bug #1735423.
* @config width
* @type String
* @default null
*/
},
// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
/**
* The default event handler fired when the "text" property is changed.
* @method configText
* @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.
*/
configText: function (type, args, obj) {
var text = args[0];
if (text) {
this.setBody(text);
}
},
/**
* The default event handler fired when the "container" property
* is changed.
* @method configContainer
* @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.
*/
configContainer: function (type, args, obj) {
var container = args[0];
if (typeof container == 'string') {
this.cfg.setProperty("container", document.getElementById(container), true);
}
},
/**
* @method _removeEventListeners
* @description Removes all of the DOM event handlers from the HTML
* element(s) that trigger the display of the tooltip.
* @protected
*/
_removeEventListeners: function () {
var aElements = this._context,
nElements,
oElement,
i;
if (aElements) {
nElements = aElements.length;
if (nElements > 0) {
i = nElements - 1;
do {
oElement = aElements[i];
Event.removeListener(oElement, "mouseover", this.onContextMouseOver);
Event.removeListener(oElement, "mousemove", this.onContextMouseMove);
Event.removeListener(oElement, "mouseout", this.onContextMouseOut);
}
while (i--);
}
}
},
/**
* The default event handler fired when the "context" property
* is changed.
* @method configContext
* @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.
*/
configContext: function (type, args, obj) {
var context = args[0],
aElements,
nElements,
oElement,
i;
if (context) {
// Normalize parameter into an array
if (! (context instanceof Array)) {
if (typeof context == "string") {
this.cfg.setProperty("context", [document.getElementById(context)], true);
} else { // Assuming this is an element
this.cfg.setProperty("context", [context], true);
}
context = this.cfg.getProperty("context");
}
// Remove any existing mouseover/mouseout listeners
this._removeEventListeners();
// Add mouseover/mouseout listeners to context elements
this._context = context;
aElements = this._context;
if (aElements) {
nElements = aElements.length;
if (nElements > 0) {
i = nElements - 1;
do {
oElement = aElements[i];
Event.on(oElement, "mouseover", this.onContextMouseOver, this);
Event.on(oElement, "mousemove", this.onContextMouseMove, this);
Event.on(oElement, "mouseout", this.onContextMouseOut, this);
}
while (i--);
}
}
}
},
// END BUILT-IN PROPERTY EVENT HANDLERS //
// BEGIN BUILT-IN DOM EVENT HANDLERS //
/**
* The default event handler fired when the user moves the mouse while
* over the context element.
* @method onContextMouseMove
* @param {DOMEvent} e The current DOM event
* @param {Object} obj The object argument
*/
onContextMouseMove: function (e, obj) {
obj.pageX = Event.getPageX(e);
obj.pageY = Event.getPageY(e);
},
/**
* The default event handler fired when the user mouses over the
* context element.
* @method onContextMouseOver
* @param {DOMEvent} e The current DOM event
* @param {Object} obj The object argument
*/
onContextMouseOver: function (e, obj) {
var context = this;
if (context.title) {
obj._tempTitle = context.title;
context.title = "";
}
// Fire first, to honor disabled set in the listner
if (obj.fireEvent("contextMouseOver", context, e) !== false
&& !obj.cfg.getProperty("disabled")) {
// Stop the tooltip from being hidden (set on last mouseout)
if (obj.hideProcId) {
clearTimeout(obj.hideProcId);
obj.logger.log("Clearing hide timer: " + obj.hideProcId, "time");
obj.hideProcId = null;
}
Event.on(context, "mousemove", obj.onContextMouseMove, obj);
/**
* The unique process ID associated with the thread responsible
* for showing the Tooltip.
* @type int
*/
obj.showProcId = obj.doShow(e, context);
obj.logger.log("Setting show tooltip timeout: " + obj.showProcId, "time");
}
},
/**
* The default event handler fired when the user mouses out of
* the context element.
* @method onContextMouseOut
* @param {DOMEvent} e The current DOM event
* @param {Object} obj The object argument
*/
onContextMouseOut: function (e, obj) {
var el = this;
if (obj._tempTitle) {
el.title = obj._tempTitle;
obj._tempTitle = null;
}
if (obj.showProcId) {
clearTimeout(obj.showProcId);
obj.logger.log("Clearing show timer: " + obj.showProcId, "time");
obj.showProcId = null;
}
if (obj.hideProcId) {
clearTimeout(obj.hideProcId);
obj.logger.log("Clearing hide timer: " + obj.hideProcId, "time");
obj.hideProcId = null;
}
obj.fireEvent("contextMouseOut", el, e);
obj.hideProcId = setTimeout(function () {
obj.hide();
}, obj.cfg.getProperty("hidedelay"));
},
// END BUILT-IN DOM EVENT HANDLERS //
/**
* Processes the showing of the Tooltip by setting the timeout delay
* and offset of the Tooltip.
* @method doShow
* @param {DOMEvent} e The current DOM event
* @param {HTMLElement} context The current context element
* @return {Number} The process ID of the timeout function associated
* with doShow
*/
doShow: function (e, context) {
var offset = this.cfg.getProperty("xyoffset"),
xOffset = offset[0],
yOffset = offset[1],
me = this;
if (UA.opera && context.tagName &&
context.tagName.toUpperCase() == "A") {
yOffset += 12;
}
return setTimeout(function () {
var txt = me.cfg.getProperty("text");
// title does not over-ride text
if (me._tempTitle && (txt === "" || YAHOO.lang.isUndefined(txt) || YAHOO.lang.isNull(txt))) {
me.setBody(me._tempTitle);
} else {
me.cfg.refireEvent("text");
}
me.logger.log("Show tooltip", "time");
me.moveTo(me.pageX + xOffset, me.pageY + yOffset);
if (me.cfg.getProperty("preventoverlap")) {
me.preventOverlap(me.pageX, me.pageY);
}
Event.removeListener(context, "mousemove", me.onContextMouseMove);
me.contextTriggerEvent.fire(context);
me.show();
me.hideProcId = me.doHide();
me.logger.log("Hide tooltip time active: " + me.hideProcId, "time");
}, this.cfg.getProperty("showdelay"));
},
/**
* Sets the timeout for the auto-dismiss delay, which by default is 5
* seconds, meaning that a tooltip will automatically dismiss itself
* after 5 seconds of being displayed.
* @method doHide
*/
doHide: function () {
var me = this;
me.logger.log("Setting hide tooltip timeout", "time");
return setTimeout(function () {
me.logger.log("Hide tooltip", "time");
me.hide();
}, this.cfg.getProperty("autodismissdelay"));
},
/**
* Fired when the Tooltip is moved, this event handler is used to
* prevent the Tooltip from overlapping with its context element.
* @method preventOverlay
* @param {Number} pageX The x coordinate position of the mouse pointer
* @param {Number} pageY The y coordinate position of the mouse pointer
*/
preventOverlap: function (pageX, pageY) {
var height = this.element.offsetHeight,
mousePoint = new YAHOO.util.Point(pageX, pageY),
elementRegion = Dom.getRegion(this.element);
elementRegion.top -= 5;
elementRegion.left -= 5;
elementRegion.right += 5;
elementRegion.bottom += 5;
this.logger.log("context " + elementRegion, "ttip");
this.logger.log("mouse " + mousePoint, "ttip");
if (elementRegion.contains(mousePoint)) {
this.logger.log("OVERLAP", "warn");
this.cfg.setProperty("y", (pageY - height - 5));
}
},
/**
* @method onRender
* @description "render" event handler for the Tooltip.
* @param {String} p_sType String representing the name of the event
* that was fired.
* @param {Array} p_aArgs Array of arguments sent when the event
* was fired.
*/
onRender: function (p_sType, p_aArgs) {
function sizeShadow() {
var oElement = this.element,
oShadow = this.underlay;
if (oShadow) {
oShadow.style.width = (oElement.offsetWidth + 6) + "px";
oShadow.style.height = (oElement.offsetHeight + 1) + "px";
}
}
function addShadowVisibleClass() {
Dom.addClass(this.underlay, "yui-tt-shadow-visible");
if (UA.ie) {
this.forceUnderlayRedraw();
}
}
function removeShadowVisibleClass() {
Dom.removeClass(this.underlay, "yui-tt-shadow-visible");
}
function createShadow() {
var oShadow = this.underlay,
oElement,
Module,
nIE,
me;
if (!oShadow) {
oElement = this.element;
Module = YAHOO.widget.Module;
nIE = UA.ie;
me = this;
if (!m_oShadowTemplate) {
m_oShadowTemplate = document.createElement("div");
m_oShadowTemplate.className = "yui-tt-shadow";
}
oShadow = m_oShadowTemplate.cloneNode(false);
oElement.appendChild(oShadow);
this.underlay = oShadow;
// Backward compatibility, even though it's probably
// intended to be "private", it isn't marked as such in the api docs
this._shadow = this.underlay;
addShadowVisibleClass.call(this);
this.subscribe("beforeShow", addShadowVisibleClass);
this.subscribe("hide", removeShadowVisibleClass);
if (bIEQuirks) {
window.setTimeout(function () {
sizeShadow.call(me);
}, 0);
this.cfg.subscribeToConfigEvent("width", sizeShadow);
this.cfg.subscribeToConfigEvent("height", sizeShadow);
this.subscribe("changeContent", sizeShadow);
Module.textResizeEvent.subscribe(sizeShadow, this, true);
this.subscribe("destroy", function () {
Module.textResizeEvent.unsubscribe(sizeShadow, this);
});
}
}
}
function onBeforeShow() {
createShadow.call(this);
this.unsubscribe("beforeShow", onBeforeShow);
}
if (this.cfg.getProperty("visible")) {
createShadow.call(this);
} else {
this.subscribe("beforeShow", onBeforeShow);
}
},
/**
* 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 tt = this;
Dom.addClass(tt.underlay, "yui-force-redraw");
setTimeout(function() {Dom.removeClass(tt.underlay, "yui-force-redraw");}, 0);
},
/**
* Removes the Tooltip element from the DOM and sets all child
* elements to null.
* @method destroy
*/
destroy: function () {
// Remove any existing mouseover/mouseout listeners
this._removeEventListeners();
Tooltip.superclass.destroy.call(this);
},
/**
* Returns a string representation of the object.
* @method toString
* @return {String} The string representation of the Tooltip
*/
toString: function () {
return "Tooltip " + this.id;
}
});
}());
(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;
}
});
}());
(function () {
/**
* <p>
* Dialog is an implementation of Panel that can be used to submit form
* data.
* </p>
* <p>
* Built-in functionality for buttons with event handlers is included.
* If the optional YUI Button dependancy is included on the page, the buttons
* created will be instances of YAHOO.widget.Button, otherwise regular HTML buttons
* will be created.
* </p>
* <p>
* Forms can be processed in 3 ways -- via an asynchronous Connection utility call,
* a simple form POST or GET, or manually. The YUI Connection utility should be
* included if you're using the default "async" postmethod, but is not required if
* you're using any of the other postmethod values.
* </p>
* @namespace YAHOO.widget
* @class Dialog
* @extends YAHOO.widget.Panel
* @constructor
* @param {String} el The element ID representing the Dialog <em>OR</em>
* @param {HTMLElement} el The element representing the Dialog
* @param {Object} userConfig The configuration object literal containing
* the configuration that should be set for this Dialog. See configuration
* documentation for more details.
*/
YAHOO.widget.Dialog = function (el, userConfig) {
YAHOO.widget.Dialog.superclass.constructor.call(this, el, userConfig);
};
var Event = YAHOO.util.Event,
CustomEvent = YAHOO.util.CustomEvent,
Dom = YAHOO.util.Dom,
Dialog = YAHOO.widget.Dialog,
Lang = YAHOO.lang,
/**
* Constant representing the name of the Dialog's events
* @property EVENT_TYPES
* @private
* @final
* @type Object
*/
EVENT_TYPES = {
"BEFORE_SUBMIT": "beforeSubmit",
"SUBMIT": "submit",
"MANUAL_SUBMIT": "manualSubmit",
"ASYNC_SUBMIT": "asyncSubmit",
"FORM_SUBMIT": "formSubmit",
"CANCEL": "cancel"
},
/**
* Constant representing the Dialog's configuration properties
* @property DEFAULT_CONFIG
* @private
* @final
* @type Object
*/
DEFAULT_CONFIG = {
"POST_METHOD": {
key: "postmethod",
value: "async"
},
"POST_DATA" : {
key: "postdata",
value: null
},
"BUTTONS": {
key: "buttons",
value: "none",
supercedes: ["visible"]
},
"HIDEAFTERSUBMIT" : {
key: "hideaftersubmit",
value: true
}
};
/**
* Constant representing the default CSS class used for a Dialog
* @property YAHOO.widget.Dialog.CSS_DIALOG
* @static
* @final
* @type String
*/
Dialog.CSS_DIALOG = "yui-dialog";
function removeButtonEventHandlers() {
var aButtons = this._aButtons,
nButtons,
oButton,
i;
if (Lang.isArray(aButtons)) {
nButtons = aButtons.length;
if (nButtons > 0) {
i = nButtons - 1;
do {
oButton = aButtons[i];
if (YAHOO.widget.Button && oButton instanceof YAHOO.widget.Button) {
oButton.destroy();
}
else if (oButton.tagName.toUpperCase() == "BUTTON") {
Event.purgeElement(oButton);
Event.purgeElement(oButton, false);
}
}
while (i--);
}
}
}
YAHOO.extend(Dialog, YAHOO.widget.Panel, {
/**
* @property form
* @description Object reference to the Dialog's
* <code><form></code> element.
* @default null
* @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
* level-one-html.html#ID-40002357">HTMLFormElement</a>
*/
form: null,
/**
* Initializes the class's configurable properties which can be changed
* using the Dialog's Config object (cfg).
* @method initDefaultConfig
*/
initDefaultConfig: function () {
Dialog.superclass.initDefaultConfig.call(this);
/**
* The internally maintained callback object for use with the
* Connection utility. The format of the callback object is
* similar to Connection Manager's callback object and is
* simply passed through to Connection Manager when the async
* request is made.
* @property callback
* @type Object
*/
this.callback = {
/**
* The function to execute upon success of the
* Connection submission (when the form does not
* contain a file input element).
*
* @property callback.success
* @type Function
*/
success: null,
/**
* The function to execute upon failure of the
* Connection submission
* @property callback.failure
* @type Function
*/
failure: null,
/**
*<p>
* The function to execute upon success of the
* Connection submission, when the form contains
* a file input element.
* </p>
* <p>
* <em>NOTE:</em> Connection manager will not
* invoke the success or failure handlers for the file
* upload use case. This will be the only callback
* handler invoked.
* </p>
* <p>
* For more information, see the <a href="http://developer.yahoo.com/yui/connection/#file">
* Connection Manager documenation on file uploads</a>.
* </p>
* @property callback.upload
* @type Function
*/
/**
* The arbitraty argument or arguments to pass to the Connection
* callback functions
* @property callback.argument
* @type Object
*/
argument: null
};
// Add form dialog config properties //
/**
* The method to use for posting the Dialog's form. Possible values
* are "async", "form", and "manual".
* @config postmethod
* @type String
* @default async
*/
this.cfg.addProperty(DEFAULT_CONFIG.POST_METHOD.key, {
handler: this.configPostMethod,
value: DEFAULT_CONFIG.POST_METHOD.value,
validator: function (val) {
if (val != "form" && val != "async" && val != "none" &&
val != "manual") {
return false;
} else {
return true;
}
}
});
/**
* Any additional post data which needs to be sent when using the
* <a href="#config_postmethod">async</a> postmethod for dialog POST submissions.
* The format for the post data string is defined by Connection Manager's
* <a href="YAHOO.util.Connect.html#method_asyncRequest">asyncRequest</a>
* method.
* @config postdata
* @type String
* @default null
*/
this.cfg.addProperty(DEFAULT_CONFIG.POST_DATA.key, {
value: DEFAULT_CONFIG.POST_DATA.value
});
/**
* This property is used to configure whether or not the
* dialog should be automatically hidden after submit.
*
* @config hideaftersubmit
* @type Boolean
* @default true
*/
this.cfg.addProperty(DEFAULT_CONFIG.HIDEAFTERSUBMIT.key, {
value: DEFAULT_CONFIG.HIDEAFTERSUBMIT.value
});
/**
* Array of object literals, each containing a set of properties
* defining a button to be appended into the Dialog's footer.
*
* <p>Each button object in the buttons array can have three properties:</p>
* <dl>
* <dt>text:</dt>
* <dd>
* The text that will display on the face of the button. The text can
* include HTML, as long as it is compliant with HTML Button specifications.
* </dd>
* <dt>handler:</dt>
* <dd>Can be either:
* <ol>
* <li>A reference to a function that should fire when the
* button is clicked. (In this case scope of this function is
* always its Dialog instance.)</li>
*
* <li>An object literal representing the code to be
* executed when the button is clicked.
*
* <p>Format:</p>
*
* <p>
* <code>{
* <br>
* <strong>fn:</strong> Function, //
* The handler to call when the event fires.
* <br>
* <strong>obj:</strong> Object, //
* An object to pass back to the handler.
* <br>
* <strong>scope:</strong> Object //
* The object to use for the scope of the handler.
* <br>
* }</code>
* </p>
* </li>
* </ol>
* </dd>
* <dt>isDefault:</dt>
* <dd>
* An optional boolean value that specifies that a button
* should be highlighted and focused by default.
* </dd>
* </dl>
*
* <em>NOTE:</em>If the YUI Button Widget is included on the page,
* the buttons created will be instances of YAHOO.widget.Button.
* Otherwise, HTML Buttons (<code><BUTTON></code>) will be
* created.
*
* @config buttons
* @type {Array|String}
* @default "none"
*/
this.cfg.addProperty(DEFAULT_CONFIG.BUTTONS.key, {
handler: this.configButtons,
value: DEFAULT_CONFIG.BUTTONS.value,
supercedes : DEFAULT_CONFIG.BUTTONS.supercedes
});
},
/**
* Initializes the custom events for Dialog which are fired
* automatically at appropriate times by the Dialog class.
* @method initEvents
*/
initEvents: function () {
Dialog.superclass.initEvents.call(this);
var SIGNATURE = CustomEvent.LIST;
/**
* CustomEvent fired prior to submission
* @event beforeSubmitEvent
*/
this.beforeSubmitEvent =
this.createEvent(EVENT_TYPES.BEFORE_SUBMIT);
this.beforeSubmitEvent.signature = SIGNATURE;
/**
* CustomEvent fired after submission
* @event submitEvent
*/
this.submitEvent = this.createEvent(EVENT_TYPES.SUBMIT);
this.submitEvent.signature = SIGNATURE;
/**
* CustomEvent fired for manual submission, before the generic submit event is fired
* @event manualSubmitEvent
*/
this.manualSubmitEvent =
this.createEvent(EVENT_TYPES.MANUAL_SUBMIT);
this.manualSubmitEvent.signature = SIGNATURE;
/**
* CustomEvent fired after asynchronous submission, before the generic submit event is fired
*
* @event asyncSubmitEvent
* @param {Object} conn The connection object, returned by YAHOO.util.Connect.asyncRequest
*/
this.asyncSubmitEvent = this.createEvent(EVENT_TYPES.ASYNC_SUBMIT);
this.asyncSubmitEvent.signature = SIGNATURE;
/**
* CustomEvent fired after form-based submission, before the generic submit event is fired
* @event formSubmitEvent
*/
this.formSubmitEvent = this.createEvent(EVENT_TYPES.FORM_SUBMIT);
this.formSubmitEvent.signature = SIGNATURE;
/**
* CustomEvent fired after cancel
* @event cancelEvent
*/
this.cancelEvent = this.createEvent(EVENT_TYPES.CANCEL);
this.cancelEvent.signature = SIGNATURE;
},
/**
* The Dialog initialization method, which is executed for Dialog 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 Dialog <em>OR</em>
* @param {HTMLElement} el The element representing the Dialog
* @param {Object} userConfig The configuration object literal
* containing the configuration that should be set for this Dialog.
* 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
*/
Dialog.superclass.init.call(this, el/*, userConfig*/);
this.beforeInitEvent.fire(Dialog);
Dom.addClass(this.element, Dialog.CSS_DIALOG);
this.cfg.setProperty("visible", false);
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
this.showEvent.subscribe(this.focusFirst, this, true);
this.beforeHideEvent.subscribe(this.blurButtons, this, true);
this.subscribe("changeBody", this.registerForm);
this.initEvent.fire(Dialog);
},
/**
* Submits the Dialog's form depending on the value of the
* "postmethod" configuration property. <strong>Please note:
* </strong> As of version 2.3 this method will automatically handle
* asyncronous file uploads should the Dialog instance's form contain
* <code><input type="file"></code> elements. If a Dialog
* instance will be handling asyncronous file uploads, its
* <code>callback</code> property will need to be setup with a
* <code>upload</code> handler rather than the standard
* <code>success</code> and, or <code>failure</code> handlers. For more
* information, see the <a href="http://developer.yahoo.com/yui/
* connection/#file">Connection Manager documenation on file uploads</a>.
* @method doSubmit
*/
doSubmit: function () {
var Connect = YAHOO.util.Connect,
oForm = this.form,
bUseFileUpload = false,
bUseSecureFileUpload = false,
aElements,
nElements,
i,
formAttrs;
switch (this.cfg.getProperty("postmethod")) {
case "async":
aElements = oForm.elements;
nElements = aElements.length;
if (nElements > 0) {
i = nElements - 1;
do {
if (aElements[i].type == "file") {
bUseFileUpload = true;
break;
}
}
while(i--);
}
if (bUseFileUpload && YAHOO.env.ua.ie && this.isSecure) {
bUseSecureFileUpload = true;
}
formAttrs = this._getFormAttributes(oForm);
Connect.setForm(oForm, bUseFileUpload, bUseSecureFileUpload);
var postData = this.cfg.getProperty("postdata");
var c = Connect.asyncRequest(formAttrs.method, formAttrs.action, this.callback, postData);
this.asyncSubmitEvent.fire(c);
break;
case "form":
oForm.submit();
this.formSubmitEvent.fire();
break;
case "none":
case "manual":
this.manualSubmitEvent.fire();
break;
}
},
/**
* Retrieves important attributes (currently method and action) from
* the form element, accounting for any elements which may have the same name
* as the attributes. Defaults to "POST" and "" for method and action respectively
* if the attribute cannot be retrieved.
*
* @method _getFormAttributes
* @protected
* @param {HTMLFormElement} oForm The HTML Form element from which to retrieve the attributes
* @return {Object} Object literal, with method and action String properties.
*/
_getFormAttributes : function(oForm){
var attrs = {
method : null,
action : null
};
if (oForm) {
if (oForm.getAttributeNode) {
var action = oForm.getAttributeNode("action");
var method = oForm.getAttributeNode("method");
if (action) {
attrs.action = action.value;
}
if (method) {
attrs.method = method.value;
}
} else {
attrs.action = oForm.getAttribute("action");
attrs.method = oForm.getAttribute("method");
}
}
attrs.method = (Lang.isString(attrs.method) ? attrs.method : "POST").toUpperCase();
attrs.action = Lang.isString(attrs.action) ? attrs.action : "";
return attrs;
},
/**
* Prepares the Dialog's internal FORM object, creating one if one is
* not currently present.
* @method registerForm
*/
registerForm: function() {
var form = this.element.getElementsByTagName("form")[0];
if (this.form) {
if (this.form == form && Dom.isAncestor(this.element, this.form)) {
return;
} else {
Event.purgeElement(this.form);
this.form = null;
}
}
if (!form) {
form = document.createElement("form");
form.name = "frm_" + this.id;
this.body.appendChild(form);
}
if (form) {
this.form = form;
Event.on(form, "submit", this._submitHandler, this, true);
}
},
/**
* Internal handler for the form submit event
*
* @method _submitHandler
* @protected
* @param {DOMEvent} e The DOM Event object
*/
_submitHandler : function(e) {
Event.stopEvent(e);
this.submit();
this.form.blur();
},
/**
* 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) {
firstElement = firstElement || this.firstButton;
lastElement = this.lastButton || lastElement;
Dialog.superclass.setTabLoop.call(this, firstElement, lastElement);
},
/**
* Configures instance properties, pointing to the
* first and last focusable elements in the Dialog's form.
*
* @method setFirstLastFocusable
*/
setFirstLastFocusable : function() {
Dialog.superclass.setFirstLastFocusable.call(this);
var i, l, el, elements = this.focusableElements;
this.firstFormElement = null;
this.lastFormElement = null;
if (this.form && elements && elements.length > 0) {
l = elements.length;
for (i = 0; i < l; ++i) {
el = elements[i];
if (this.form === el.form) {
this.firstFormElement = el;
break;
}
}
for (i = l-1; i >= 0; --i) {
el = elements[i];
if (this.form === el.form) {
this.lastFormElement = el;
break;
}
}
}
},
// 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 Dialog.
* @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) {
Dialog.superclass.configClose.apply(this, arguments);
},
/**
* Event handler for the close icon
*
* @method _doClose
* @protected
*
* @param {DOMEvent} e
*/
_doClose : function(e) {
Event.preventDefault(e);
this.cancel();
},
/**
* The default event handler for the "buttons" configuration property
* @method configButtons
* @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.
*/
configButtons: function (type, args, obj) {
var Button = YAHOO.widget.Button,
aButtons = args[0],
oInnerElement = this.innerElement,
oButton,
oButtonEl,
oYUIButton,
nButtons,
oSpan,
oFooter,
i;
removeButtonEventHandlers.call(this);
this._aButtons = null;
if (Lang.isArray(aButtons)) {
oSpan = document.createElement("span");
oSpan.className = "button-group";
nButtons = aButtons.length;
this._aButtons = [];
this.defaultHtmlButton = null;
for (i = 0; i < nButtons; i++) {
oButton = aButtons[i];
if (Button) {
oYUIButton = new Button({ label: oButton.text});
oYUIButton.appendTo(oSpan);
oButtonEl = oYUIButton.get("element");
if (oButton.isDefault) {
oYUIButton.addClass("default");
this.defaultHtmlButton = oButtonEl;
}
if (Lang.isFunction(oButton.handler)) {
oYUIButton.set("onclick", {
fn: oButton.handler,
obj: this,
scope: this
});
} else if (Lang.isObject(oButton.handler) && Lang.isFunction(oButton.handler.fn)) {
oYUIButton.set("onclick", {
fn: oButton.handler.fn,
obj: ((!Lang.isUndefined(oButton.handler.obj)) ? oButton.handler.obj : this),
scope: (oButton.handler.scope || this)
});
}
this._aButtons[this._aButtons.length] = oYUIButton;
} else {
oButtonEl = document.createElement("button");
oButtonEl.setAttribute("type", "button");
if (oButton.isDefault) {
oButtonEl.className = "default";
this.defaultHtmlButton = oButtonEl;
}
oButtonEl.innerHTML = oButton.text;
if (Lang.isFunction(oButton.handler)) {
Event.on(oButtonEl, "click", oButton.handler, this, true);
} else if (Lang.isObject(oButton.handler) &&
Lang.isFunction(oButton.handler.fn)) {
Event.on(oButtonEl, "click",
oButton.handler.fn,
((!Lang.isUndefined(oButton.handler.obj)) ? oButton.handler.obj : this),
(oButton.handler.scope || this));
}
oSpan.appendChild(oButtonEl);
this._aButtons[this._aButtons.length] = oButtonEl;
}
oButton.htmlButton = oButtonEl;
if (i === 0) {
this.firstButton = oButtonEl;
}
if (i == (nButtons - 1)) {
this.lastButton = oButtonEl;
}
}
this.setFooter(oSpan);
oFooter = this.footer;
if (Dom.inDocument(this.element) && !Dom.isAncestor(oInnerElement, oFooter)) {
oInnerElement.appendChild(oFooter);
}
this.buttonSpan = oSpan;
} else { // Do cleanup
oSpan = this.buttonSpan;
oFooter = this.footer;
if (oSpan && oFooter) {
oFooter.removeChild(oSpan);
this.buttonSpan = null;
this.firstButton = null;
this.lastButton = null;
this.defaultHtmlButton = null;
}
}
this.changeContentEvent.fire();
},
/**
* @method getButtons
* @description Returns an array containing each of the Dialog's
* buttons, by default an array of HTML <code><BUTTON></code>
* elements. If the Dialog's buttons were created using the
* YAHOO.widget.Button class (via the inclusion of the optional Button
* dependancy on the page), an array of YAHOO.widget.Button instances
* is returned.
* @return {Array}
*/
getButtons: function () {
return this._aButtons || null;
},
/**
* <p>
* Sets focus to the first focusable element in the Dialog's form if found,
* else, the default button if found, else the first button defined via the
* "buttons" configuration property.
* </p>
* <p>
* This method is invoked when the Dialog is made visible.
* </p>
* @method focusFirst
*/
focusFirst: function (type, args, obj) {
var el = this.firstFormElement;
if (args && args[1]) {
Event.stopEvent(args[1]);
}
if (el) {
try {
el.focus();
} catch(oException) {
// Ignore
}
} else {
if (this.defaultHtmlButton) {
this.focusDefaultButton();
} else {
this.focusFirstButton();
}
}
},
/**
* Sets focus to the last element in the Dialog's form or the last
* button defined via the "buttons" configuration property.
* @method focusLast
*/
focusLast: function (type, args, obj) {
var aButtons = this.cfg.getProperty("buttons"),
el = this.lastFormElement;
if (args && args[1]) {
Event.stopEvent(args[1]);
}
if (aButtons && Lang.isArray(aButtons)) {
this.focusLastButton();
} else {
if (el) {
try {
el.focus();
} catch(oException) {
// Ignore
}
}
}
},
/**
* Helper method to normalize button references. It either returns the
* YUI Button instance for the given element if found,
* or the passes back the HTMLElement reference if a corresponding YUI Button
* reference is not found or YAHOO.widget.Button does not exist on the page.
*
* @method _getButton
* @private
* @param {HTMLElement} button
* @return {YAHOO.widget.Button|HTMLElement}
*/
_getButton : function(button) {
var Button = YAHOO.widget.Button;
// If we have an HTML button and YUI Button is on the page,
// get the YUI Button reference if available.
if (Button && button && button.nodeName && button.id) {
button = Button.getButton(button.id) || button;
}
return button;
},
/**
* Sets the focus to the button that is designated as the default via
* the "buttons" configuration property. By default, this method is
* called when the Dialog is made visible.
* @method focusDefaultButton
*/
focusDefaultButton: function () {
var button = this._getButton(this.defaultHtmlButton);
if (button) {
/*
Place the call to the "focus" method inside a try/catch
block to prevent IE from throwing JavaScript errors if
the element is disabled or hidden.
*/
try {
button.focus();
} catch(oException) {
}
}
},
/**
* Blurs all the buttons defined via the "buttons"
* configuration property.
* @method blurButtons
*/
blurButtons: function () {
var aButtons = this.cfg.getProperty("buttons"),
nButtons,
oButton,
oElement,
i;
if (aButtons && Lang.isArray(aButtons)) {
nButtons = aButtons.length;
if (nButtons > 0) {
i = (nButtons - 1);
do {
oButton = aButtons[i];
if (oButton) {
oElement = this._getButton(oButton.htmlButton);
if (oElement) {
/*
Place the call to the "blur" method inside
a try/catch block to prevent IE from
throwing JavaScript errors if the element
is disabled or hidden.
*/
try {
oElement.blur();
} catch(oException) {
// ignore
}
}
}
} while(i--);
}
}
},
/**
* Sets the focus to the first button created via the "buttons"
* configuration property.
* @method focusFirstButton
*/
focusFirstButton: function () {
var aButtons = this.cfg.getProperty("buttons"),
oButton,
oElement;
if (aButtons && Lang.isArray(aButtons)) {
oButton = aButtons[0];
if (oButton) {
oElement = this._getButton(oButton.htmlButton);
if (oElement) {
/*
Place the call to the "focus" method inside a
try/catch block to prevent IE from throwing
JavaScript errors if the element is disabled
or hidden.
*/
try {
oElement.focus();
} catch(oException) {
// ignore
}
}
}
}
},
/**
* Sets the focus to the last button created via the "buttons"
* configuration property.
* @method focusLastButton
*/
focusLastButton: function () {
var aButtons = this.cfg.getProperty("buttons"),
nButtons,
oButton,
oElement;
if (aButtons && Lang.isArray(aButtons)) {
nButtons = aButtons.length;
if (nButtons > 0) {
oButton = aButtons[(nButtons - 1)];
if (oButton) {
oElement = this._getButton(oButton.htmlButton);
if (oElement) {
/*
Place the call to the "focus" method inside a
try/catch block to prevent IE from throwing
JavaScript errors if the element is disabled
or hidden.
*/
try {
oElement.focus();
} catch(oException) {
// Ignore
}
}
}
}
}
},
/**
* The default event handler for the "postmethod" configuration property
* @method configPostMethod
* @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.
*/
configPostMethod: function (type, args, obj) {
this.registerForm();
},
// END BUILT-IN PROPERTY EVENT HANDLERS //
/**
* Built-in function hook for writing a validation function that will
* be checked for a "true" value prior to a submit. This function, as
* implemented by default, always returns true, so it should be
* overridden if validation is necessary.
* @method validate
*/
validate: function () {
return true;
},
/**
* Executes a submit of the Dialog if validation
* is successful. By default the Dialog is hidden
* after submission, but you can set the "hideaftersubmit"
* configuration property to false, to prevent the Dialog
* from being hidden.
*
* @method submit
*/
submit: function () {
if (this.validate()) {
if (this.beforeSubmitEvent.fire()) {
this.doSubmit();
this.submitEvent.fire();
if (this.cfg.getProperty("hideaftersubmit")) {
this.hide();
}
return true;
} else {
return false;
}
} else {
return false;
}
},
/**
* Executes the cancel of the Dialog followed by a hide.
* @method cancel
*/
cancel: function () {
this.cancelEvent.fire();
this.hide();
},
/**
* Returns a JSON-compatible data structure representing the data
* currently contained in the form.
* @method getData
* @return {Object} A JSON object reprsenting the data of the
* current form.
*/
getData: function () {
var oForm = this.form,
aElements,
nTotalElements,
oData,
sName,
oElement,
nElements,
sType,
sTagName,
aOptions,
nOptions,
aValues,
oOption,
oRadio,
oCheckbox,
valueAttr,
i,
n;
function isFormElement(p_oElement) {
var sTag = p_oElement.tagName.toUpperCase();
return ((sTag == "INPUT" || sTag == "TEXTAREA" ||
sTag == "SELECT") && p_oElement.name == sName);
}
if (oForm) {
aElements = oForm.elements;
nTotalElements = aElements.length;
oData = {};
for (i = 0; i < nTotalElements; i++) {
sName = aElements[i].name;
/*
Using "Dom.getElementsBy" to safeguard user from JS
errors that result from giving a form field (or set of
fields) the same name as a native method of a form
(like "submit") or a DOM collection (such as the "item"
method). Originally tried accessing fields via the
"namedItem" method of the "element" collection, but
discovered that it won't return a collection of fields
in Gecko.
*/
oElement = Dom.getElementsBy(isFormElement, "*", oForm);
nElements = oElement.length;
if (nElements > 0) {
if (nElements == 1) {
oElement = oElement[0];
sType = oElement.type;
sTagName = oElement.tagName.toUpperCase();
switch (sTagName) {
case "INPUT":
if (sType == "checkbox") {
oData[sName] = oElement.checked;
} else if (sType != "radio") {
oData[sName] = oElement.value;
}
break;
case "TEXTAREA":
oData[sName] = oElement.value;
break;
case "SELECT":
aOptions = oElement.options;
nOptions = aOptions.length;
aValues = [];
for (n = 0; n < nOptions; n++) {
oOption = aOptions[n];
if (oOption.selected) {
valueAttr = oOption.attributes.value;
aValues[aValues.length] = (valueAttr && valueAttr.specified) ? oOption.value : oOption.text;
}
}
oData[sName] = aValues;
break;
}
} else {
sType = oElement[0].type;
switch (sType) {
case "radio":
for (n = 0; n < nElements; n++) {
oRadio = oElement[n];
if (oRadio.checked) {
oData[sName] = oRadio.value;
break;
}
}
break;
case "checkbox":
aValues = [];
for (n = 0; n < nElements; n++) {
oCheckbox = oElement[n];
if (oCheckbox.checked) {
aValues[aValues.length] = oCheckbox.value;
}
}
oData[sName] = aValues;
break;
}
}
}
}
}
return oData;
},
/**
* Removes the Panel element from the DOM and sets all child elements
* to null.
* @method destroy
*/
destroy: function () {
removeButtonEventHandlers.call(this);
this._aButtons = null;
var aForms = this.element.getElementsByTagName("form"),
oForm;
if (aForms.length > 0) {
oForm = aForms[0];
if (oForm) {
Event.purgeElement(oForm);
if (oForm.parentNode) {
oForm.parentNode.removeChild(oForm);
}
this.form = null;
}
}
Dialog.superclass.destroy.call(this);
},
/**
* Returns a string representation of the object.
* @method toString
* @return {String} The string representation of the Dialog
*/
toString: function () {
return "Dialog " + this.id;
}
});
}());
(function () {
/**
* SimpleDialog is a simple implementation of Dialog that can be used to
* submit a single value. Forms can be processed in 3 ways -- via an
* asynchronous Connection utility call, a simple form POST or GET,
* or manually.
* @namespace YAHOO.widget
* @class SimpleDialog
* @extends YAHOO.widget.Dialog
* @constructor
* @param {String} el The element ID representing the SimpleDialog
* <em>OR</em>
* @param {HTMLElement} el The element representing the SimpleDialog
* @param {Object} userConfig The configuration object literal containing
* the configuration that should be set for this SimpleDialog. See
* configuration documentation for more details.
*/
YAHOO.widget.SimpleDialog = function (el, userConfig) {
YAHOO.widget.SimpleDialog.superclass.constructor.call(this,
el, userConfig);
};
var Dom = YAHOO.util.Dom,
SimpleDialog = YAHOO.widget.SimpleDialog,
/**
* Constant representing the SimpleDialog's configuration properties
* @property DEFAULT_CONFIG
* @private
* @final
* @type Object
*/
DEFAULT_CONFIG = {
"ICON": {
key: "icon",
value: "none",
suppressEvent: true
},
"TEXT": {
key: "text",
value: "",
suppressEvent: true,
supercedes: ["icon"]
}
};
/**
* Constant for the standard network icon for a blocking action
* @property YAHOO.widget.SimpleDialog.ICON_BLOCK
* @static
* @final
* @type String
*/
SimpleDialog.ICON_BLOCK = "blckicon";
/**
* Constant for the standard network icon for alarm
* @property YAHOO.widget.SimpleDialog.ICON_ALARM
* @static
* @final
* @type String
*/
SimpleDialog.ICON_ALARM = "alrticon";
/**
* Constant for the standard network icon for help
* @property YAHOO.widget.SimpleDialog.ICON_HELP
* @static
* @final
* @type String
*/
SimpleDialog.ICON_HELP = "hlpicon";
/**
* Constant for the standard network icon for info
* @property YAHOO.widget.SimpleDialog.ICON_INFO
* @static
* @final
* @type String
*/
SimpleDialog.ICON_INFO = "infoicon";
/**
* Constant for the standard network icon for warn
* @property YAHOO.widget.SimpleDialog.ICON_WARN
* @static
* @final
* @type String
*/
SimpleDialog.ICON_WARN = "warnicon";
/**
* Constant for the standard network icon for a tip
* @property YAHOO.widget.SimpleDialog.ICON_TIP
* @static
* @final
* @type String
*/
SimpleDialog.ICON_TIP = "tipicon";
/**
* Constant representing the name of the CSS class applied to the element
* created by the "icon" configuration property.
* @property YAHOO.widget.SimpleDialog.ICON_CSS_CLASSNAME
* @static
* @final
* @type String
*/
SimpleDialog.ICON_CSS_CLASSNAME = "yui-icon";
/**
* Constant representing the default CSS class used for a SimpleDialog
* @property YAHOO.widget.SimpleDialog.CSS_SIMPLEDIALOG
* @static
* @final
* @type String
*/
SimpleDialog.CSS_SIMPLEDIALOG = "yui-simple-dialog";
YAHOO.extend(SimpleDialog, YAHOO.widget.Dialog, {
/**
* Initializes the class's configurable properties which can be changed
* using the SimpleDialog's Config object (cfg).
* @method initDefaultConfig
*/
initDefaultConfig: function () {
SimpleDialog.superclass.initDefaultConfig.call(this);
// Add dialog config properties //
/**
* Sets the informational icon for the SimpleDialog
* @config icon
* @type String
* @default "none"
*/
this.cfg.addProperty(DEFAULT_CONFIG.ICON.key, {
handler: this.configIcon,
value: DEFAULT_CONFIG.ICON.value,
suppressEvent: DEFAULT_CONFIG.ICON.suppressEvent
});
/**
* Sets the text for the SimpleDialog
* @config text
* @type String
* @default ""
*/
this.cfg.addProperty(DEFAULT_CONFIG.TEXT.key, {
handler: this.configText,
value: DEFAULT_CONFIG.TEXT.value,
suppressEvent: DEFAULT_CONFIG.TEXT.suppressEvent,
supercedes: DEFAULT_CONFIG.TEXT.supercedes
});
},
/**
* The SimpleDialog initialization method, which is executed for
* SimpleDialog 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 SimpleDialog
* <em>OR</em>
* @param {HTMLElement} el The element representing the SimpleDialog
* @param {Object} userConfig The configuration object literal
* containing the configuration that should be set for this
* SimpleDialog. 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
*/
SimpleDialog.superclass.init.call(this, el/*, userConfig*/);
this.beforeInitEvent.fire(SimpleDialog);
Dom.addClass(this.element, SimpleDialog.CSS_SIMPLEDIALOG);
this.cfg.queueProperty("postmethod", "manual");
if (userConfig) {
this.cfg.applyConfig(userConfig, true);
}
this.beforeRenderEvent.subscribe(function () {
if (! this.body) {
this.setBody("");
}
}, this, true);
this.initEvent.fire(SimpleDialog);
},
/**
* Prepares the SimpleDialog's internal FORM object, creating one if one
* is not currently present, and adding the value hidden field.
* @method registerForm
*/
registerForm: function () {
SimpleDialog.superclass.registerForm.call(this);
this.form.innerHTML += "<input type=\"hidden\" name=\"" +
this.id + "\" value=\"\"/>";
},
// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
/**
* Fired when the "icon" property is set.
* @method configIcon
* @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.
*/
configIcon: function (type,args,obj) {
var sIcon = args[0],
oBody = this.body,
sCSSClass = SimpleDialog.ICON_CSS_CLASSNAME,
aElements,
oIcon,
oIconParent;
if (sIcon && sIcon != "none") {
aElements = Dom.getElementsByClassName(sCSSClass, "*" , oBody);
if (aElements.length === 1) {
oIcon = aElements[0];
oIconParent = oIcon.parentNode;
if (oIconParent) {
oIconParent.removeChild(oIcon);
oIcon = null;
}
}
if (sIcon.indexOf(".") == -1) {
oIcon = document.createElement("span");
oIcon.className = (sCSSClass + " " + sIcon);
oIcon.innerHTML = " ";
} else {
oIcon = document.createElement("img");
oIcon.src = (this.imageRoot + sIcon);
oIcon.className = sCSSClass;
}
if (oIcon) {
oBody.insertBefore(oIcon, oBody.firstChild);
}
}
},
/**
* Fired when the "text" property is set.
* @method configText
* @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.
*/
configText: function (type,args,obj) {
var text = args[0];
if (text) {
this.setBody(text);
this.cfg.refireEvent("icon");
}
},
// END BUILT-IN PROPERTY EVENT HANDLERS //
/**
* Returns a string representation of the object.
* @method toString
* @return {String} The string representation of the SimpleDialog
*/
toString: function () {
return "SimpleDialog " + this.id;
}
/**
* <p>
* Sets the SimpleDialog's body content to the HTML specified.
* If no body is present, one will be automatically created.
* An empty string can be passed to the method to clear the contents of the body.
* </p>
* <p><strong>NOTE:</strong> SimpleDialog provides the <a href="#config_text">text</a>
* and <a href="#config_icon">icon</a> configuration properties to set the contents
* of it's body element in accordance with the UI design for a SimpleDialog (an
* icon and message text). Calling setBody on the SimpleDialog will not enforce this
* UI design constraint and will replace the entire contents of the SimpleDialog body.
* It should only be used if you wish the replace the default icon/text body structure
* of a SimpleDialog with your own custom markup.</p>
*
* @method setBody
* @param {String} bodyContent The HTML used to set the body.
* As a convenience, non HTMLElement objects can also be passed into
* the method, and will be treated as strings, with the body innerHTML
* set to their default toString implementations.
* <em>OR</em>
* @param {HTMLElement} bodyContent The HTMLElement to add as the first and only child of the body element.
* <em>OR</em>
* @param {DocumentFragment} bodyContent The document fragment
* containing elements which are to be added to the body
*/
});
}());
(function () {
/**
* ContainerEffect encapsulates animation transitions that are executed when
* an Overlay is shown or hidden.
* @namespace YAHOO.widget
* @class ContainerEffect
* @constructor
* @param {YAHOO.widget.Overlay} overlay The Overlay that the animation
* should be associated with
* @param {Object} attrIn The object literal representing the animation
* arguments to be used for the animate-in transition. The arguments for
* this literal are: attributes(object, see YAHOO.util.Anim for description),
* duration(Number), and method(i.e. Easing.easeIn).
* @param {Object} attrOut The object literal representing the animation
* arguments to be used for the animate-out transition. The arguments for
* this literal are: attributes(object, see YAHOO.util.Anim for description),
* duration(Number), and method(i.e. Easing.easeIn).
* @param {HTMLElement} targetElement Optional. The target element that
* should be animated during the transition. Defaults to overlay.element.
* @param {class} Optional. The animation class to instantiate. Defaults to
* YAHOO.util.Anim. Other options include YAHOO.util.Motion.
*/
YAHOO.widget.ContainerEffect = function (overlay, attrIn, attrOut, targetElement, animClass) {
if (!animClass) {
animClass = YAHOO.util.Anim;
}
/**
* The overlay to animate
* @property overlay
* @type YAHOO.widget.Overlay
*/
this.overlay = overlay;
/**
* The animation attributes to use when transitioning into view
* @property attrIn
* @type Object
*/
this.attrIn = attrIn;
/**
* The animation attributes to use when transitioning out of view
* @property attrOut
* @type Object
*/
this.attrOut = attrOut;
/**
* The target element to be animated
* @property targetElement
* @type HTMLElement
*/
this.targetElement = targetElement || overlay.element;
/**
* The animation class to use for animating the overlay
* @property animClass
* @type class
*/
this.animClass = animClass;
};
var Dom = YAHOO.util.Dom,
CustomEvent = YAHOO.util.CustomEvent,
ContainerEffect = YAHOO.widget.ContainerEffect;
/**
* A pre-configured ContainerEffect instance that can be used for fading
* an overlay in and out.
* @method FADE
* @static
* @param {YAHOO.widget.Overlay} overlay The Overlay object to animate
* @param {Number} dur The duration of the animation
* @return {YAHOO.widget.ContainerEffect} The configured ContainerEffect object
*/
ContainerEffect.FADE = function (overlay, dur) {
var Easing = YAHOO.util.Easing,
fin = {
attributes: {opacity:{from:0, to:1}},
duration: dur,
method: Easing.easeIn
},
fout = {
attributes: {opacity:{to:0}},
duration: dur,
method: Easing.easeOut
},
fade = new ContainerEffect(overlay, fin, fout, overlay.element);
fade.handleUnderlayStart = function() {
var underlay = this.overlay.underlay;
if (underlay && YAHOO.env.ua.ie) {
var hasFilters = (underlay.filters && underlay.filters.length > 0);
if(hasFilters) {
Dom.addClass(overlay.element, "yui-effect-fade");
}
}
};
fade.handleUnderlayComplete = function() {
var underlay = this.overlay.underlay;
if (underlay && YAHOO.env.ua.ie) {
Dom.removeClass(overlay.element, "yui-effect-fade");
}
};
fade.handleStartAnimateIn = function (type, args, obj) {
Dom.addClass(obj.overlay.element, "hide-select");
if (!obj.overlay.underlay) {
obj.overlay.cfg.refireEvent("underlay");
}
obj.handleUnderlayStart();
obj.overlay._setDomVisibility(true);
Dom.setStyle(obj.overlay.element, "opacity", 0);
};
fade.handleCompleteAnimateIn = function (type,args,obj) {
Dom.removeClass(obj.overlay.element, "hide-select");
if (obj.overlay.element.style.filter) {
obj.overlay.element.style.filter = null;
}
obj.handleUnderlayComplete();
obj.overlay.cfg.refireEvent("iframe");
obj.animateInCompleteEvent.fire();
};
fade.handleStartAnimateOut = function (type, args, obj) {
Dom.addClass(obj.overlay.element, "hide-select");
obj.handleUnderlayStart();
};
fade.handleCompleteAnimateOut = function (type, args, obj) {
Dom.removeClass(obj.overlay.element, "hide-select");
if (obj.overlay.element.style.filter) {
obj.overlay.element.style.filter = null;
}
obj.overlay._setDomVisibility(false);
Dom.setStyle(obj.overlay.element, "opacity", 1);
obj.handleUnderlayComplete();
obj.overlay.cfg.refireEvent("iframe");
obj.animateOutCompleteEvent.fire();
};
fade.init();
return fade;
};
/**
* A pre-configured ContainerEffect instance that can be used for sliding an
* overlay in and out.
* @method SLIDE
* @static
* @param {YAHOO.widget.Overlay} overlay The Overlay object to animate
* @param {Number} dur The duration of the animation
* @return {YAHOO.widget.ContainerEffect} The configured ContainerEffect object
*/
ContainerEffect.SLIDE = function (overlay, dur) {
var Easing = YAHOO.util.Easing,
x = overlay.cfg.getProperty("x") || Dom.getX(overlay.element),
y = overlay.cfg.getProperty("y") || Dom.getY(overlay.element),
clientWidth = Dom.getClientWidth(),
offsetWidth = overlay.element.offsetWidth,
sin = {
attributes: { points: { to: [x, y] } },
duration: dur,
method: Easing.easeIn
},
sout = {
attributes: { points: { to: [(clientWidth + 25), y] } },
duration: dur,
method: Easing.easeOut
},
slide = new ContainerEffect(overlay, sin, sout, overlay.element, YAHOO.util.Motion);
slide.handleStartAnimateIn = function (type,args,obj) {
obj.overlay.element.style.left = ((-25) - offsetWidth) + "px";
obj.overlay.element.style.top = y + "px";
};
slide.handleTweenAnimateIn = function (type, args, obj) {
var pos = Dom.getXY(obj.overlay.element),
currentX = pos[0],
currentY = pos[1];
if (Dom.getStyle(obj.overlay.element, "visibility") ==
"hidden" && currentX < x) {
obj.overlay._setDomVisibility(true);
}
obj.overlay.cfg.setProperty("xy", [currentX, currentY], true);
obj.overlay.cfg.refireEvent("iframe");
};
slide.handleCompleteAnimateIn = function (type, args, obj) {
obj.overlay.cfg.setProperty("xy", [x, y], true);
obj.startX = x;
obj.startY = y;
obj.overlay.cfg.refireEvent("iframe");
obj.animateInCompleteEvent.fire();
};
slide.handleStartAnimateOut = function (type, args, obj) {
var vw = Dom.getViewportWidth(),
pos = Dom.getXY(obj.overlay.element),
yso = pos[1];
obj.animOut.attributes.points.to = [(vw + 25), yso];
};
slide.handleTweenAnimateOut = function (type, args, obj) {
var pos = Dom.getXY(obj.overlay.element),
xto = pos[0],
yto = pos[1];
obj.overlay.cfg.setProperty("xy", [xto, yto], true);
obj.overlay.cfg.refireEvent("iframe");
};
slide.handleCompleteAnimateOut = function (type, args, obj) {
obj.overlay._setDomVisibility(false);
obj.overlay.cfg.setProperty("xy", [x, y]);
obj.animateOutCompleteEvent.fire();
};
slide.init();
return slide;
};
ContainerEffect.prototype = {
/**
* Initializes the animation classes and events.
* @method init
*/
init: function () {
this.beforeAnimateInEvent = this.createEvent("beforeAnimateIn");
this.beforeAnimateInEvent.signature = CustomEvent.LIST;
this.beforeAnimateOutEvent = this.createEvent("beforeAnimateOut");
this.beforeAnimateOutEvent.signature = CustomEvent.LIST;
this.animateInCompleteEvent = this.createEvent("animateInComplete");
this.animateInCompleteEvent.signature = CustomEvent.LIST;
this.animateOutCompleteEvent =
this.createEvent("animateOutComplete");
this.animateOutCompleteEvent.signature = CustomEvent.LIST;
this.animIn = new this.animClass(this.targetElement,
this.attrIn.attributes, this.attrIn.duration,
this.attrIn.method);
this.animIn.onStart.subscribe(this.handleStartAnimateIn, this);
this.animIn.onTween.subscribe(this.handleTweenAnimateIn, this);
this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,
this);
this.animOut = new this.animClass(this.targetElement,
this.attrOut.attributes, this.attrOut.duration,
this.attrOut.method);
this.animOut.onStart.subscribe(this.handleStartAnimateOut, this);
this.animOut.onTween.subscribe(this.handleTweenAnimateOut, this);
this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut,
this);
},
/**
* Triggers the in-animation.
* @method animateIn
*/
animateIn: function () {
this.beforeAnimateInEvent.fire();
this.animIn.animate();
},
/**
* Triggers the out-animation.
* @method animateOut
*/
animateOut: function () {
this.beforeAnimateOutEvent.fire();
this.animOut.animate();
},
/**
* The default onStart handler for the in-animation.
* @method handleStartAnimateIn
* @param {String} type The CustomEvent type
* @param {Object[]} args The CustomEvent arguments
* @param {Object} obj The scope object
*/
handleStartAnimateIn: function (type, args, obj) { },
/**
* The default onTween handler for the in-animation.
* @method handleTweenAnimateIn
* @param {String} type The CustomEvent type
* @param {Object[]} args The CustomEvent arguments
* @param {Object} obj The scope object
*/
handleTweenAnimateIn: function (type, args, obj) { },
/**
* The default onComplete handler for the in-animation.
* @method handleCompleteAnimateIn
* @param {String} type The CustomEvent type
* @param {Object[]} args The CustomEvent arguments
* @param {Object} obj The scope object
*/
handleCompleteAnimateIn: function (type, args, obj) { },
/**
* The default onStart handler for the out-animation.
* @method handleStartAnimateOut
* @param {String} type The CustomEvent type
* @param {Object[]} args The CustomEvent arguments
* @param {Object} obj The scope object
*/
handleStartAnimateOut: function (type, args, obj) { },
/**
* The default onTween handler for the out-animation.
* @method handleTweenAnimateOut
* @param {String} type The CustomEvent type
* @param {Object[]} args The CustomEvent arguments
* @param {Object} obj The scope object
*/
handleTweenAnimateOut: function (type, args, obj) { },
/**
* The default onComplete handler for the out-animation.
* @method handleCompleteAnimateOut
* @param {String} type The CustomEvent type
* @param {Object[]} args The CustomEvent arguments
* @param {Object} obj The scope object
*/
handleCompleteAnimateOut: function (type, args, obj) { },
/**
* Returns a string representation of the object.
* @method toString
* @return {String} The string representation of the ContainerEffect
*/
toString: function () {
var output = "ContainerEffect";
if (this.overlay) {
output += " [" + this.overlay.toString() + "]";
}
return output;
}
};
YAHOO.lang.augmentProto(ContainerEffect, YAHOO.util.EventProvider);
})();
YAHOO.register("container", YAHOO.widget.Module, {version: "2.8.1", build: "19"});