# Yahoo! UI Library

### Container 2.9.0

Yahoo! UI Library > container > Module.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    (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);

    }());

Copyright © 2011 Yahoo! Inc. All rights reserved.