(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";
/**
* CSS classname representing the module header. NOTE: The classname is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
* @property YAHOO.widget.Module.CSS_HEADER
* @static
* @final
* @type String
*/
Module.CSS_HEADER = "hd";
/**
* CSS classname representing the module body. NOTE: The classname is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
* @property YAHOO.widget.Module.CSS_BODY
* @static
* @final
* @type String
*/
Module.CSS_BODY = "bd";
/**
* CSS classname representing the module footer. NOTE: The classname is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
* @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 identifying whether the current platform is windows or mac. This property
* currently only identifies these 2 platforms, and returns false otherwise.
* @property platform
* @deprecated Use YAHOO.env.ua
* @type {String|Boolean}
*/
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|Boolean}
*/
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, {
handler: this.configEffect,
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";
oIFrame.tabIndex = -1;
oIFrame.setAttribute("role", "presentation");
/*
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 markup 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 {HTML} headerContent The markup used to set the header content.
* 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.
*
* <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
*
* <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 {HTML} bodyContent The HTML used to set the body content
* 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.
*
* <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
*
* <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 {HTML} 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.
*
* <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
*
* <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, sets all child elements to null, and purges the bounding element of event listeners.
* @method destroy
* @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners.
* NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
*/
destroy: function (shallowPurge) {
var parent,
purgeChildren = !(shallowPurge);
if (this.element) {
Event.purgeElement(this.element, purgeChildren);
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) {
if(this.beforeShowEvent.fire()) {
Dom.setStyle(this.element, "display", "block");
this.showEvent.fire();
}
} else {
if (this.beforeHideEvent.fire()) {
Dom.setStyle(this.element, "display", "none");
this.hideEvent.fire();
}
}
},
/**
* Default event handler for the "effect" 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 configEffect
*/
configEffect: function (type, args, obj) {
this._cachedEffects = (this.cacheEffects) ? this._createEffects(args[0]) : null;
},
/**
* If true, ContainerEffects (and Anim instances) are cached when "effect" is set, and reused.
* If false, new instances are created each time the container is hidden or shown, as was the
* behavior prior to 2.9.0.
*
* @property cacheEffects
* @since 2.9.0
* @default true
* @type boolean
*/
cacheEffects : true,
/**
* Creates an array of ContainerEffect instances from the provided configs
*
* @method _createEffects
* @param {Array|Object} effectCfg An effect configuration or array of effect configurations
* @return {Array} An array of ContainerEffect instances.
* @protected
*/
_createEffects: function(effectCfg) {
var effectInstances = null,
n,
i,
eff;
if (effectCfg) {
if (effectCfg instanceof Array) {
effectInstances = [];
n = effectCfg.length;
for (i = 0; i < n; i++) {
eff = effectCfg[i];
if (eff.effect) {
effectInstances[effectInstances.length] = eff.effect(this, eff.duration);
}
}
} else if (effectCfg.effect) {
effectInstances = [effectCfg.effect(this, effectCfg.duration)];
}
}
return effectInstances;
},
/**
* 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);
}());