- /**
- * Provides stackable (z-index) support for Widgets through an extension.
- *
- * @module widget-stack
- */
- var L = Y.Lang,
- UA = Y.UA,
- Node = Y.Node,
- Widget = Y.Widget,
-
- ZINDEX = "zIndex",
- SHIM = "shim",
- VISIBLE = "visible",
-
- BOUNDING_BOX = "boundingBox",
-
- RENDER_UI = "renderUI",
- BIND_UI = "bindUI",
- SYNC_UI = "syncUI",
-
- OFFSET_WIDTH = "offsetWidth",
- OFFSET_HEIGHT = "offsetHeight",
- PARENT_NODE = "parentNode",
- FIRST_CHILD = "firstChild",
- OWNER_DOCUMENT = "ownerDocument",
-
- WIDTH = "width",
- HEIGHT = "height",
- PX = "px",
-
- // HANDLE KEYS
- SHIM_DEFERRED = "shimdeferred",
- SHIM_RESIZE = "shimresize",
-
- // Events
- VisibleChange = "visibleChange",
- WidthChange = "widthChange",
- HeightChange = "heightChange",
- ShimChange = "shimChange",
- ZIndexChange = "zIndexChange",
- ContentUpdate = "contentUpdate",
-
- // CSS
- STACKED = "stacked";
-
- /**
- * Widget extension, which can be used to add stackable (z-index) support to the
- * base Widget class along with a shimming solution, through the
- * <a href="Base.html#method_build">Base.build</a> method.
- *
- * @class WidgetStack
- * @param {Object} User configuration object
- */
- function Stack(config) {}
-
- // Static Properties
- /**
- * Static property used to define the default attribute
- * configuration introduced by WidgetStack.
- *
- * @property ATTRS
- * @type Object
- * @static
- */
- Stack.ATTRS = {
- /**
- * @attribute shim
- * @type boolean
- * @default false, for all browsers other than IE6, for which a shim is enabled by default.
- *
- * @description Boolean flag to indicate whether or not a shim should be added to the Widgets
- * boundingBox, to protect it from select box bleedthrough.
- */
- shim: {
- value: (UA.ie == 6)
- },
-
- /**
- * @attribute zIndex
- * @type number
- * @default 0
- * @description The z-index to apply to the Widgets boundingBox. Non-numerical values for
- * zIndex will be converted to 0
- */
- zIndex: {
- value : 0,
- setter: '_setZIndex'
- }
- };
-
- /**
- * The HTML parsing rules for the WidgetStack class.
- *
- * @property HTML_PARSER
- * @static
- * @type Object
- */
- Stack.HTML_PARSER = {
- zIndex: function (srcNode) {
- return this._parseZIndex(srcNode);
- }
- };
-
- /**
- * Default class used to mark the shim element
- *
- * @property SHIM_CLASS_NAME
- * @type String
- * @static
- * @default "yui3-widget-shim"
- */
- Stack.SHIM_CLASS_NAME = Widget.getClassName(SHIM);
-
- /**
- * Default class used to mark the boundingBox of a stacked widget.
- *
- * @property STACKED_CLASS_NAME
- * @type String
- * @static
- * @default "yui3-widget-stacked"
- */
- Stack.STACKED_CLASS_NAME = Widget.getClassName(STACKED);
-
- /**
- * Default markup template used to generate the shim element.
- *
- * @property SHIM_TEMPLATE
- * @type String
- * @static
- */
- Stack.SHIM_TEMPLATE = '<iframe class="' + Stack.SHIM_CLASS_NAME + '" frameborder="0" title="Widget Stacking Shim" src="javascript:false" tabindex="-1" role="presentation"></iframe>';
-
- Stack.prototype = {
-
- initializer : function() {
- this._stackNode = this.get(BOUNDING_BOX);
- this._stackHandles = {};
-
- // WIDGET METHOD OVERLAP
- Y.after(this._renderUIStack, this, RENDER_UI);
- Y.after(this._syncUIStack, this, SYNC_UI);
- Y.after(this._bindUIStack, this, BIND_UI);
- },
-
- /**
- * Synchronizes the UI to match the Widgets stack state. This method in
- * invoked after syncUI is invoked for the Widget class using YUI's aop infrastructure.
- *
- * @method _syncUIStack
- * @protected
- */
- _syncUIStack: function() {
- this._uiSetShim(this.get(SHIM));
- this._uiSetZIndex(this.get(ZINDEX));
- },
-
- /**
- * Binds event listeners responsible for updating the UI state in response to
- * Widget stack related state changes.
- * <p>
- * This method is invoked after bindUI is invoked for the Widget class
- * using YUI's aop infrastructure.
- * </p>
- * @method _bindUIStack
- * @protected
- */
- _bindUIStack: function() {
- this.after(ShimChange, this._afterShimChange);
- this.after(ZIndexChange, this._afterZIndexChange);
- },
-
- /**
- * Creates/Initializes the DOM to support stackability.
- * <p>
- * This method in invoked after renderUI is invoked for the Widget class
- * using YUI's aop infrastructure.
- * </p>
- * @method _renderUIStack
- * @protected
- */
- _renderUIStack: function() {
- this._stackNode.addClass(Stack.STACKED_CLASS_NAME);
- },
-
- /**
- Parses a `zIndex` attribute value from this widget's `srcNode`.
-
- @method _parseZIndex
- @param {Node} srcNode The node to parse a `zIndex` value from.
- @return {Mixed} The parsed `zIndex` value.
- @protected
- **/
- _parseZIndex: function (srcNode) {
- var zIndex;
-
- // Prefers how WebKit handles `z-index` which better matches the
- // spec:
- //
- // * http://www.w3.org/TR/CSS2/visuren.html#z-index
- // * https://bugs.webkit.org/show_bug.cgi?id=15562
- //
- // When a node isn't rendered in the document, and/or when a
- // node is not positioned, then it doesn't have a context to derive
- // a valid `z-index` value from.
- if (!srcNode.inDoc() || srcNode.getStyle('position') === 'static') {
- zIndex = 'auto';
- } else {
- // Uses `getComputedStyle()` because it has greater accuracy in
- // more browsers than `getStyle()` does for `z-index`.
- zIndex = srcNode.getComputedStyle('zIndex');
- }
-
- // This extension adds a stacking context to widgets, therefore a
- // `srcNode` witout a stacking context (i.e. "auto") will return
- // `null` from this DOM parser. This way the widget's default or
- // user provided value for `zIndex` will be used.
- return zIndex === 'auto' ? null : zIndex;
- },
-
- /**
- * Default setter for zIndex attribute changes. Normalizes zIndex values to
- * numbers, converting non-numerical values to 0.
- *
- * @method _setZIndex
- * @protected
- * @param {String | Number} zIndex
- * @return {Number} Normalized zIndex
- */
- _setZIndex: function(zIndex) {
- if (L.isString(zIndex)) {
- zIndex = parseInt(zIndex, 10);
- }
- if (!L.isNumber(zIndex)) {
- zIndex = 0;
- }
- return zIndex;
- },
-
- /**
- * Default attribute change listener for the shim attribute, responsible
- * for updating the UI, in response to attribute changes.
- *
- * @method _afterShimChange
- * @protected
- * @param {EventFacade} e The event facade for the attribute change
- */
- _afterShimChange : function(e) {
- this._uiSetShim(e.newVal);
- },
-
- /**
- * Default attribute change listener for the zIndex attribute, responsible
- * for updating the UI, in response to attribute changes.
- *
- * @method _afterZIndexChange
- * @protected
- * @param {EventFacade} e The event facade for the attribute change
- */
- _afterZIndexChange : function(e) {
- this._uiSetZIndex(e.newVal);
- },
-
- /**
- * Updates the UI to reflect the zIndex value passed in.
- *
- * @method _uiSetZIndex
- * @protected
- * @param {number} zIndex The zindex to be reflected in the UI
- */
- _uiSetZIndex: function (zIndex) {
- this._stackNode.setStyle(ZINDEX, zIndex);
- },
-
- /**
- * Updates the UI to enable/disable the shim. If the widget is not currently visible,
- * creation of the shim is deferred until it is made visible, for performance reasons.
- *
- * @method _uiSetShim
- * @protected
- * @param {boolean} enable If true, creates/renders the shim, if false, removes it.
- */
- _uiSetShim: function (enable) {
- if (enable) {
- // Lazy creation
- if (this.get(VISIBLE)) {
- this._renderShim();
- } else {
- this._renderShimDeferred();
- }
-
- // Eagerly attach resize handlers
- //
- // Required because of Event stack behavior, commit ref: cd8dddc
- // Should be revisted after Ticket #2531067 is resolved.
- if (UA.ie == 6) {
- this._addShimResizeHandlers();
- }
- } else {
- this._destroyShim();
- }
- },
-
- /**
- * Sets up change handlers for the visible attribute, to defer shim creation/rendering
- * until the Widget is made visible.
- *
- * @method _renderShimDeferred
- * @private
- */
- _renderShimDeferred : function() {
-
- this._stackHandles[SHIM_DEFERRED] = this._stackHandles[SHIM_DEFERRED] || [];
-
- var handles = this._stackHandles[SHIM_DEFERRED],
- createBeforeVisible = function(e) {
- if (e.newVal) {
- this._renderShim();
- }
- };
-
- handles.push(this.on(VisibleChange, createBeforeVisible));
- // Depending how how Ticket #2531067 is resolved, a reversal of
- // commit ref: cd8dddc could lead to a more elagent solution, with
- // the addition of this line here:
- //
- // handles.push(this.after(VisibleChange, this.sizeShim));
- },
-
- /**
- * Sets up event listeners to resize the shim when the size of the Widget changes.
- * <p>
- * NOTE: This method is only used for IE6 currently, since IE6 doesn't support a way to
- * resize the shim purely through CSS, when the Widget does not have an explicit width/height
- * set.
- * </p>
- * @method _addShimResizeHandlers
- * @private
- */
- _addShimResizeHandlers : function() {
-
- this._stackHandles[SHIM_RESIZE] = this._stackHandles[SHIM_RESIZE] || [];
-
- var sizeShim = this.sizeShim,
- handles = this._stackHandles[SHIM_RESIZE];
-
- handles.push(this.after(VisibleChange, sizeShim));
- handles.push(this.after(WidthChange, sizeShim));
- handles.push(this.after(HeightChange, sizeShim));
- handles.push(this.after(ContentUpdate, sizeShim));
- },
-
- /**
- * Detaches any handles stored for the provided key
- *
- * @method _detachStackHandles
- * @param String handleKey The key defining the group of handles which should be detached
- * @private
- */
- _detachStackHandles : function(handleKey) {
- var handles = this._stackHandles[handleKey],
- handle;
-
- if (handles && handles.length > 0) {
- while((handle = handles.pop())) {
- handle.detach();
- }
- }
- },
-
- /**
- * Creates the shim element and adds it to the DOM
- *
- * @method _renderShim
- * @private
- */
- _renderShim : function() {
- var shimEl = this._shimNode,
- stackEl = this._stackNode;
-
- if (!shimEl) {
- shimEl = this._shimNode = this._getShimTemplate();
- stackEl.insertBefore(shimEl, stackEl.get(FIRST_CHILD));
-
- this._detachStackHandles(SHIM_DEFERRED);
- this.sizeShim();
- }
- },
-
- /**
- * Removes the shim from the DOM, and detaches any related event
- * listeners.
- *
- * @method _destroyShim
- * @private
- */
- _destroyShim : function() {
- if (this._shimNode) {
- this._shimNode.get(PARENT_NODE).removeChild(this._shimNode);
- this._shimNode = null;
-
- this._detachStackHandles(SHIM_DEFERRED);
- this._detachStackHandles(SHIM_RESIZE);
- }
- },
-
- /**
- * For IE6, synchronizes the size and position of iframe shim to that of
- * Widget bounding box which it is protecting. For all other browsers,
- * this method does not do anything.
- *
- * @method sizeShim
- */
- sizeShim: function () {
- var shim = this._shimNode,
- node = this._stackNode;
-
- if (shim && UA.ie === 6 && this.get(VISIBLE)) {
- shim.setStyle(WIDTH, node.get(OFFSET_WIDTH) + PX);
- shim.setStyle(HEIGHT, node.get(OFFSET_HEIGHT) + PX);
- }
- },
-
- /**
- * Creates a cloned shim node, using the SHIM_TEMPLATE html template, for use on a new instance.
- *
- * @method _getShimTemplate
- * @private
- * @return {Node} node A new shim Node instance.
- */
- _getShimTemplate : function() {
- return Node.create(Stack.SHIM_TEMPLATE, this._stackNode.get(OWNER_DOCUMENT));
- }
- };
-
- Y.WidgetStack = Stack;
-
-