#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: widget-stack/js/Widget-Stack.js

    /**
     * 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) {
            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);
        }

        // 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 = {

            /**
             * 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;