Index: openacs-4/packages/ajaxhelper/www/resources/yui/container/container-debug.js =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/ajaxhelper/www/resources/yui/container/container-debug.js,v diff -u -r1.3 -r1.4 --- openacs-4/packages/ajaxhelper/www/resources/yui/container/container-debug.js 8 Sep 2007 14:22:01 -0000 1.3 +++ openacs-4/packages/ajaxhelper/www/resources/yui/container/container-debug.js 9 Apr 2009 17:03:49 -0000 1.4 @@ -1,8 +1,8 @@ /* -Copyright (c) 2007, Yahoo! Inc. All rights reserved. +Copyright (c) 2009, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt -version: 2.3.0 +version: 2.7.0 */ (function () { @@ -18,27 +18,21 @@ * @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"); - - } - + + if (!owner) { YAHOO.log("No owner specified for Config object", "error", "Config"); } + }; var Lang = YAHOO.lang, - CustomEvent = YAHOO.util.CustomEvent, + 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 @@ -157,7 +151,7 @@ * @param {value} Object The value of the correct type for the property */ fireEvent: function ( key, value ) { - YAHOO.log("Firing Config event: " + key + "=" + value, "info"); + YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config"); var property = this.config[key]; if (property && property.event) { @@ -174,7 +168,7 @@ */ addProperty: function ( key, propertyObject ) { key = key.toLowerCase(); - YAHOO.log("Added property: " + key, "info"); + YAHOO.log("Added property: " + key, "info", "Config"); this.config[key] = propertyObject; @@ -206,16 +200,19 @@ getConfig: function () { var cfg = {}, + currCfg = this.config, prop, property; - for (prop in this.config) { - property = this.config[prop]; - if (property && property.event) { - cfg[prop] = property.value; + for (prop in currCfg) { + if (Lang.hasOwnProperty(currCfg, prop)) { + property = currCfg[prop]; + if (property && property.event) { + cfg[prop] = property.value; + } } } - + return cfg; }, @@ -279,7 +276,7 @@ var property; key = key.toLowerCase(); - YAHOO.log("setProperty: " + key + "=" + value, "info"); + YAHOO.log("setProperty: " + key + "=" + value, "info", "Config"); if (this.queueInProgress && ! silent) { // Currently running through a queue... @@ -318,7 +315,7 @@ queueProperty: function (key, value) { key = key.toLowerCase(); - YAHOO.log("queueProperty: " + key + "=" + value, "info"); + YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config"); var property = this.config[key], foundDuplicate = false, @@ -358,7 +355,7 @@ if (queueItem) { queueItemKey = queueItem[0]; queueItemValue = queueItem[1]; - + if (queueItemKey == key) { /* @@ -386,38 +383,38 @@ } 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"); - + YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config"); + return true; } else { return false; @@ -466,37 +463,23 @@ applyConfig: function (userConfig, init) { var sKey, - oValue, 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]); - } - } - }, /** @@ -505,11 +488,13 @@ * @method refresh */ refresh: function () { - + var prop; - + for (prop in this.config) { - this.refireEvent(prop); + if (Lang.hasOwnProperty(this.config, prop)) { + this.refireEvent(prop); + } } }, @@ -533,9 +518,14 @@ 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); } } @@ -563,19 +553,12 @@ var property = this.config[key.toLowerCase()]; if (property && property.event) { - if (!Config.alreadySubscribed(property.event, handler, obj)) { - property.event.subscribe(handler, obj, override); - } - return true; - } else { - return false; - } }, @@ -695,31 +678,24 @@ 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 () { /** @@ -752,27 +728,21 @@ * 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, @@ -786,7 +756,6 @@ * @type Object */ EVENT_TYPES = { - "BEFORE_INIT": "beforeInit", "INIT": "init", "APPEND": "append", @@ -801,7 +770,6 @@ "SHOW": "show", "BEFORE_HIDE": "beforeHide", "HIDE": "hide" - }, /** @@ -818,21 +786,24 @@ value: true, validator: YAHOO.lang.isBoolean }, - - "EFFECT": { - key: "effect", - suppressEvent: true, - supercedes: ["visible"] + + "EFFECT": { + key: "effect", + suppressEvent: true, + supercedes: ["visible"] }, - - "MONITOR_RESIZE": { - key: "monitorresize", - value: true + + "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 @@ -868,7 +839,7 @@ * @type String */ Module.CSS_HEADER = "hd"; - + /** * Constant representing the module body * @property YAHOO.widget.Module.CSS_BODY @@ -896,20 +867,46 @@ * @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 = ("
+ * NOTE: 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. + *
* @config effect * @type Object * @default null @@ -1264,7 +1214,7 @@ 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 @@ -1276,15 +1226,36 @@ 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". + *+ * 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. + *
+ * + * @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. + *+ * If the element passed in does not have an id, one will be generated + * for it. + *
* @method init * @param {String} el The element ID representing the Module OR * @param {HTMLElement} el The element representing the Module @@ -1293,321 +1264,323 @@ * See configuration documentation for more details. */ init: function (el, userConfig) { - - var elId, i, child; - + + 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") { + 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; - - if (el.id) { - this.id = el.id; - } - + child = this.element.firstChild; - - if (child) { + if (child) { + var fndHd = false, fndBd = false, fndFt = false; do { - - switch (child.className) { - - case Module.CSS_HEADER: - - this.header = child; - - break; - - case Module.CSS_BODY: - - this.body = child; - - break; - - case Module.CSS_FOOTER: - - this.footer = child; - - break; - + // 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)) { + if (!Config.alreadySubscribed(this.renderEvent, this.cfg.fireQueue, this.cfg)) { this.renderEvent.subscribe(this.cfg.fireQueue, this.cfg, true); - } - + this.initEvent.fire(Module); }, - + /** - * Initialized an empty IFRAME that is placed out of the visible area + * 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 (!YAHOO.env.ua.opera) { - + + 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 && - YAHOO.env.ua.ie) { - + + 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 = ["" + - ""; - - oIFrame.src = "data:text/html;charset=utf-8," + - encodeURIComponent(sHTML); - + 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 SourceForge bug #1723064) */ - oIFrame.style.position = "absolute"; oIFrame.style.visibility = "hidden"; - - document.body.appendChild(oIFrame); - - oIFrame.style.width = "10em"; - oIFrame.style.height = "10em"; - oIFrame.style.top = (-1 * oIFrame.offsetHeight) + "px"; - oIFrame.style.left = (-1 * oIFrame.offsetWidth) + "px"; + + var db = document.body, + fc = db.firstChild; + if (fc) { + db.insertBefore(oIFrame, fc); + } else { + db.appendChild(oIFrame); + } + + oIFrame.style.width = "2em"; + oIFrame.style.height = "2em"; + oIFrame.style.top = (-1 * (oIFrame.offsetHeight + Module.RESIZE_MONITOR_BUFFER)) + "px"; + oIFrame.style.left = "0"; oIFrame.style.borderWidth = "0"; oIFrame.style.visibility = "visible"; - - if (YAHOO.env.ua.webkit) { - + + /* + Don't open/close the document for Gecko like we used to, since it + leads to duplicate cookies. (See SourceForge bug #1721755) + */ + if (UA.webkit) { oDoc = oIFrame.contentWindow.document; - oDoc.open(); oDoc.close(); - } - } - + if (oIFrame && oIFrame.contentWindow) { + Module.textResizeEvent.subscribe(this.onDomResize, this, true); - Module.textResizeEvent.subscribe(this.onDomResize, - this, true); - if (!Module.textResizeInitialized) { - - 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); - + 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 nLeft = -1 * this.resizeMonitor.offsetWidth, - nTop = -1 * this.resizeMonitor.offsetHeight; - + + var nTop = -1 * (this.resizeMonitor.offsetHeight + Module.RESIZE_MONITOR_BUFFER); + this.resizeMonitor.style.top = nTop + "px"; - this.resizeMonitor.style.left = nLeft + "px"; - + this.resizeMonitor.style.left = "0"; }, - + /** - * Sets the Module's header content to the HTML specified, or appends + * 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. + * 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 HTML used to set the header + * @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. * OR * @param {HTMLElement} headerContent The HTMLElement to append to - * the header + * OR + * @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 (typeof headerContent == "string") { - oHeader.innerHTML = headerContent; - - } else { - + if (headerContent.nodeName) { oHeader.innerHTML = ""; oHeader.appendChild(headerContent); - + } else { + oHeader.innerHTML = headerContent; } - + 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} element The element to append to the header + * @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, or appends the - * passed element to the body. If no body is present, one will be - * automatically created. + * 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 OR - * @param {HTMLElement} bodyContent The HTMLElement to append to the body + * @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. + * OR + * @param {HTMLElement} bodyContent The HTMLElement to add as the first and only + * child of the body element. + * OR + * @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 (typeof bodyContent == "string") { - oBody.innerHTML = bodyContent; - - } else { - + if (bodyContent.nodeName) { oBody.innerHTML = ""; oBody.appendChild(bodyContent); - + } else { + oBody.innerHTML = bodyContent; } - + 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} element The element to append to the body + * @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); @@ -1620,58 +1593,72 @@ /** * 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. + * 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. * OR * @param {HTMLElement} footerContent The HTMLElement to append to * the footer + * OR + * @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 (typeof footerContent == "string") { - oFooter.innerHTML = footerContent; - - } else { - + if (footerContent.nodeName) { oFooter.innerHTML = ""; oFooter.appendChild(footerContent); - + } else { + oFooter.innerHTML = footerContent; } - + 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} element The element to append to the footer + * @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. - * NOTE: For Modules without existing markup, the appendToNode argument + *+ * 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. + *
+ *+ * 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. + *
* @method render * @param {String} appendToNode The element id to which the Module * should be appended to prior to rendering OR @@ -1682,120 +1669,75 @@ * @return {Boolean} Success or failure of the render */ render: function (appendToNode, moduleElement) { - + var me = this, firstChild; - - function appendTo(element) { - if (typeof element == "string") { - element = document.getElementById(element); + + function appendTo(parentNode) { + if (typeof parentNode == "string") { + parentNode = document.getElementById(parentNode); } - - if (element) { - element.appendChild(me.element); + + if (parentNode) { + me._addToParent(parentNode, me.element); me.appendEvent.fire(); } } - + this.beforeRenderEvent.fire(); - + if (! moduleElement) { moduleElement = this.element; } - - if (appendToNode) { + if (appendToNode) { appendTo(appendToNode); - } else { - - /* - No node was passed in. If the element is not already in - the Dom, this fails - */ - + // 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"); - + YAHOO.log("Render failed. Must specify appendTo node if " + " Module isn't already in the DOM.", "error"); return false; - } - } - + // 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 - */ - + // There is a header, but it's not in the DOM yet. Need to add it. firstChild = moduleElement.firstChild; - - if (firstChild) { // Insert before first child if exists - + if (firstChild) { moduleElement.insertBefore(this.header, firstChild); - - } else { // Append to empty body because there are no children - + } else { moduleElement.appendChild(this.header); - } - } - - if (this.body && ! Dom.inDocument(this.body)) { - /* - There is a body, but it's not in the DOM yet... - need to add it - */ - - - // Insert before footer if exists in DOM - - if (this.footer && Dom.isAncestor( - this.moduleElement, this.footer)) { - + 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(this.moduleElement, this.footer)) { moduleElement.insertBefore(this.body, this.footer); - - } else { // Append to element because there is no footer - + } else { moduleElement.appendChild(this.body); - } - } - - if (this.footer && ! Dom.inDocument(this.footer)) { - /* - There is a footer, but it's not in the DOM yet... - need to add it - */ - + 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); - } - + this.renderEvent.fire(); return true; }, - + /** * Removes the Module element from the DOM and sets all child elements * to null. * @method destroy */ destroy: function () { - - var parent, - e; - + + var parent; + if (this.element) { Event.purgeElement(this.element, true); parent = this.element.parentNode; @@ -1816,15 +1758,8 @@ this.cfg = null; this.destroyEvent.fire(); - - for (e in this) { - if (e instanceof CustomEvent) { - e.unsubscribeAll(); - } - } - }, - + /** * Shows the Module element by setting the visible configuration * property to true. Also fires two events: beforeShowEvent prior to @@ -1834,7 +1769,7 @@ 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 @@ -1846,7 +1781,6 @@ }, // 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 @@ -1871,7 +1805,7 @@ this.hideEvent.fire(); } }, - + /** * Default event handler for the "monitorresize" configuration property * @param {String} type The CustomEvent type (usually the property name) @@ -1882,23 +1816,40 @@ * @method configMonitorResize */ configMonitorResize: function (type, args, obj) { - var monitor = args[0]; - if (monitor) { - this.initResizeMonitor(); - } else { - - Module.textResizeEvent.unsubscribe( - this.onDomResize, this, true); - + 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. + *+ * If the parentNode is not document.body, the element is appended as the last element. + *
+ *+ * If the parentNode is document.body the element is added as the first child to help + * prevent Operation Aborted errors in IE. + *
+ * + * @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 @@ -1907,12 +1858,12 @@ toString: function () { return "Module " + this.id; } - }; - + YAHOO.lang.augmentProto(Module, YAHOO.util.EventProvider); }()); + (function () { /** @@ -1924,7 +1875,7 @@ * properly rendered above SELECT elements. * @namespace YAHOO.widget * @class Overlay - * @extends Module + * @extends YAHOO.widget.Module * @param {String} el The element ID representing the Overlay OR * @param {HTMLElement} el The element representing the Overlay * @param {Object} userConfig The configuration object literal containing @@ -1933,20 +1884,22 @@ * @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, /** @@ -1957,12 +1910,10 @@ * @type Object */ EVENT_TYPES = { - "BEFORE_MOVE": "beforeMove", "MOVE": "move" - }, - + /** * Constant representing the Overlay's configuration properties * @property DEFAULT_CONFIG @@ -1971,74 +1922,85 @@ * @type Object */ DEFAULT_CONFIG = { - + "X": { key: "x", validator: Lang.isNumber, suppressEvent: true, - supercedes: ["iframe"] + supercedes: ["iframe"] }, - + "Y": { key: "y", validator: Lang.isNumber, suppressEvent: true, - supercedes: ["iframe"] + supercedes: ["iframe"] }, - + "XY": { key: "xy", suppressEvent: true, supercedes: ["iframe"] }, - + "CONTEXT": { key: "context", suppressEvent: true, supercedes: ["iframe"] }, - + "FIXED_CENTER": { key: "fixedcenter", value: false, - validator: Lang.isBoolean, supercedes: ["iframe", "visible"] }, - + "WIDTH": { - key: "width", - suppressEvent: true, - supercedes: ["context", "fixedcenter", "iframe"] + 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"] + supercedes: ["iframe", "x", "y", "xy"] }, - + "IFRAME": { key: "iframe", - value: (YAHOO.env.ua.ie == 6 ? true : false), + 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 @@ -2050,16 +2012,27 @@ /** * Number representing how much the iframe shim should be offset from each - * side of an Overlay instance. + * 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 @@ -2068,7 +2041,7 @@ * @type String */ Overlay.TOP_LEFT = "tl"; - + /** * Constant representing the top right corner of an element, used for * configuring the context element alignment @@ -2078,7 +2051,7 @@ * @type String */ Overlay.TOP_RIGHT = "tr"; - + /** * Constant representing the top bottom left corner of an element, used for * configuring the context element alignment @@ -2088,7 +2061,7 @@ * @type String */ Overlay.BOTTOM_LEFT = "bl"; - + /** * Constant representing the bottom right corner of an element, used for * configuring the context element alignment @@ -2098,7 +2071,7 @@ * @type String */ Overlay.BOTTOM_RIGHT = "br"; - + /** * Constant representing the default CSS class used for an Overlay * @property YAHOO.widget.Overlay.CSS_OVERLAY @@ -2107,84 +2080,89 @@ * @type String */ Overlay.CSS_OVERLAY = "yui-overlay"; - - + /** + * 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) { - - if (YAHOO.env.ua.ie) { - - if (! window.scrollEnd) { - - window.scrollEnd = -1; - + 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(); } - - 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 (YAHOO.env.ua.ie) { - + + 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. @@ -2193,19 +2171,55 @@ * @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, { - + /** + *+ * Array of default event types which will trigger + * context alignment for the Overlay class. + *
+ *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. + *
+ *
+ * E.g.:
+ * CustomOverlay.prototype.CONTEXT_TRIGGERS = YAHOO.widget.Overlay.prototype.CONTEXT_TRIGGERS.concat(["windowScroll"]);
+ *
+ * The array of context arguments for context-sensitive positioning. + *
+ * + *
+ * The format of the array is: [contextElementOrId, overlayCorner, contextCorner, arrayOfTriggerEvents (optional)]
, the
+ * the 4 array elements described in detail below:
+ *
+ * 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 align + * 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. + *
+ *
+ * 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 : "windowResize", "windowScroll", "textResize"
(defined in _TRIGGER_MAP private property).
+ *
+ * For example, setting this property to ["img1", "tl", "bl"]
will
+ * align the Overlay's top left corner to the bottom left corner of the
+ * context element with id "img1".
+ *
+ * Adding the optional trigger values: ["img1", "tl", "bl", ["beforeShow", "windowResize"]]
,
+ * will re-align the overlay position, whenever the "beforeShow" or "windowResize" events are fired.
+ *
This property can be set to:
+ * + *+ * 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. + *
+ *+ * 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. + *
+ *In this case the overlay can still be
+ * centered as a one-off operation, by invoking the center()
method,
+ * however it will not remain centered when the window is scrolled/resized.
+ *
true
option.
+ * However, unlike setting the property to true
,
+ * when the property is set to "contained"
, 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.
+ *