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