NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / widget-stack / widget-stack.js
bloba57964e8fbd5dec4cbb9332afda300bcce6b1bae
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('widget-stack', function (Y, NAME) {
10 /**
11  * Provides stackable (z-index) support for Widgets through an extension.
12  *
13  * @module widget-stack
14  */
15     var L = Y.Lang,
16         UA = Y.UA,
17         Node = Y.Node,
18         Widget = Y.Widget,
20         ZINDEX = "zIndex",
21         SHIM = "shim",
22         VISIBLE = "visible",
24         BOUNDING_BOX = "boundingBox",
26         RENDER_UI = "renderUI",
27         BIND_UI = "bindUI",
28         SYNC_UI = "syncUI",
30         OFFSET_WIDTH = "offsetWidth",
31         OFFSET_HEIGHT = "offsetHeight",
32         PARENT_NODE = "parentNode",
33         FIRST_CHILD = "firstChild",
34         OWNER_DOCUMENT = "ownerDocument",
36         WIDTH = "width",
37         HEIGHT = "height",
38         PX = "px",
40         // HANDLE KEYS
41         SHIM_DEFERRED = "shimdeferred",
42         SHIM_RESIZE = "shimresize",
44         // Events
45         VisibleChange = "visibleChange",
46         WidthChange = "widthChange",
47         HeightChange = "heightChange",
48         ShimChange = "shimChange",
49         ZIndexChange = "zIndexChange",
50         ContentUpdate = "contentUpdate",
52         // CSS
53         STACKED = "stacked";
55     /**
56      * Widget extension, which can be used to add stackable (z-index) support to the
57      * base Widget class along with a shimming solution, through the
58      * <a href="Base.html#method_build">Base.build</a> method.
59      *
60      * @class WidgetStack
61      * @param {Object} User configuration object
62      */
63     function Stack(config) {}
65     // Static Properties
66     /**
67      * Static property used to define the default attribute
68      * configuration introduced by WidgetStack.
69      *
70      * @property ATTRS
71      * @type Object
72      * @static
73      */
74     Stack.ATTRS = {
75         /**
76          * @attribute shim
77          * @type boolean
78          * @default false, for all browsers other than IE6, for which a shim is enabled by default.
79          *
80          * @description Boolean flag to indicate whether or not a shim should be added to the Widgets
81          * boundingBox, to protect it from select box bleedthrough.
82          */
83         shim: {
84             value: (UA.ie == 6)
85         },
87         /**
88          * @attribute zIndex
89          * @type number
90          * @default 0
91          * @description The z-index to apply to the Widgets boundingBox. Non-numerical values for
92          * zIndex will be converted to 0
93          */
94         zIndex: {
95             value : 0,
96             setter: '_setZIndex'
97         }
98     };
100     /**
101      * The HTML parsing rules for the WidgetStack class.
102      *
103      * @property HTML_PARSER
104      * @static
105      * @type Object
106      */
107     Stack.HTML_PARSER = {
108         zIndex: function (srcNode) {
109             return this._parseZIndex(srcNode);
110         }
111     };
113     /**
114      * Default class used to mark the shim element
115      *
116      * @property SHIM_CLASS_NAME
117      * @type String
118      * @static
119      * @default "yui3-widget-shim"
120      */
121     Stack.SHIM_CLASS_NAME = Widget.getClassName(SHIM);
123     /**
124      * Default class used to mark the boundingBox of a stacked widget.
125      *
126      * @property STACKED_CLASS_NAME
127      * @type String
128      * @static
129      * @default "yui3-widget-stacked"
130      */
131     Stack.STACKED_CLASS_NAME = Widget.getClassName(STACKED);
133     /**
134      * Default markup template used to generate the shim element.
135      *
136      * @property SHIM_TEMPLATE
137      * @type String
138      * @static
139      */
140     Stack.SHIM_TEMPLATE = '<iframe class="' + Stack.SHIM_CLASS_NAME + '" frameborder="0" title="Widget Stacking Shim" src="javascript:false" tabindex="-1" role="presentation"></iframe>';
142     Stack.prototype = {
144         initializer : function() {
145             this._stackNode = this.get(BOUNDING_BOX);
146             this._stackHandles = {};
148             // WIDGET METHOD OVERLAP
149             Y.after(this._renderUIStack, this, RENDER_UI);
150             Y.after(this._syncUIStack, this, SYNC_UI);
151             Y.after(this._bindUIStack, this, BIND_UI);
152         },
154         /**
155          * Synchronizes the UI to match the Widgets stack state. This method in
156          * invoked after syncUI is invoked for the Widget class using YUI's aop infrastructure.
157          *
158          * @method _syncUIStack
159          * @protected
160          */
161         _syncUIStack: function() {
162             this._uiSetShim(this.get(SHIM));
163             this._uiSetZIndex(this.get(ZINDEX));
164         },
166         /**
167          * Binds event listeners responsible for updating the UI state in response to
168          * Widget stack related state changes.
169          * <p>
170          * This method is invoked after bindUI is invoked for the Widget class
171          * using YUI's aop infrastructure.
172          * </p>
173          * @method _bindUIStack
174          * @protected
175          */
176         _bindUIStack: function() {
177             this.after(ShimChange, this._afterShimChange);
178             this.after(ZIndexChange, this._afterZIndexChange);
179         },
181         /**
182          * Creates/Initializes the DOM to support stackability.
183          * <p>
184          * This method in invoked after renderUI is invoked for the Widget class
185          * using YUI's aop infrastructure.
186          * </p>
187          * @method _renderUIStack
188          * @protected
189          */
190         _renderUIStack: function() {
191             this._stackNode.addClass(Stack.STACKED_CLASS_NAME);
192         },
194         /**
195         Parses a `zIndex` attribute value from this widget's `srcNode`.
197         @method _parseZIndex
198         @param {Node} srcNode The node to parse a `zIndex` value from.
199         @return {Mixed} The parsed `zIndex` value.
200         @protected
201         **/
202         _parseZIndex: function (srcNode) {
203             var zIndex;
205             // Prefers how WebKit handles `z-index` which better matches the
206             // spec:
207             //
208             // * http://www.w3.org/TR/CSS2/visuren.html#z-index
209             // * https://bugs.webkit.org/show_bug.cgi?id=15562
210             //
211             // When a node isn't rendered in the document, and/or when a
212             // node is not positioned, then it doesn't have a context to derive
213             // a valid `z-index` value from.
214             if (!srcNode.inDoc() || srcNode.getStyle('position') === 'static') {
215                 zIndex = 'auto';
216             } else {
217                 // Uses `getComputedStyle()` because it has greater accuracy in
218                 // more browsers than `getStyle()` does for `z-index`.
219                 zIndex = srcNode.getComputedStyle('zIndex');
220             }
222             // This extension adds a stacking context to widgets, therefore a
223             // `srcNode` witout a stacking context (i.e. "auto") will return
224             // `null` from this DOM parser. This way the widget's default or
225             // user provided value for `zIndex` will be used.
226             return zIndex === 'auto' ? null : zIndex;
227         },
229         /**
230          * Default setter for zIndex attribute changes. Normalizes zIndex values to
231          * numbers, converting non-numerical values to 0.
232          *
233          * @method _setZIndex
234          * @protected
235          * @param {String | Number} zIndex
236          * @return {Number} Normalized zIndex
237          */
238         _setZIndex: function(zIndex) {
239             if (L.isString(zIndex)) {
240                 zIndex = parseInt(zIndex, 10);
241             }
242             if (!L.isNumber(zIndex)) {
243                 zIndex = 0;
244             }
245             return zIndex;
246         },
248         /**
249          * Default attribute change listener for the shim attribute, responsible
250          * for updating the UI, in response to attribute changes.
251          *
252          * @method _afterShimChange
253          * @protected
254          * @param {EventFacade} e The event facade for the attribute change
255          */
256         _afterShimChange : function(e) {
257             this._uiSetShim(e.newVal);
258         },
260         /**
261          * Default attribute change listener for the zIndex attribute, responsible
262          * for updating the UI, in response to attribute changes.
263          *
264          * @method _afterZIndexChange
265          * @protected
266          * @param {EventFacade} e The event facade for the attribute change
267          */
268         _afterZIndexChange : function(e) {
269             this._uiSetZIndex(e.newVal);
270         },
272         /**
273          * Updates the UI to reflect the zIndex value passed in.
274          *
275          * @method _uiSetZIndex
276          * @protected
277          * @param {number} zIndex The zindex to be reflected in the UI
278          */
279         _uiSetZIndex: function (zIndex) {
280             this._stackNode.setStyle(ZINDEX, zIndex);
281         },
283         /**
284          * Updates the UI to enable/disable the shim. If the widget is not currently visible,
285          * creation of the shim is deferred until it is made visible, for performance reasons.
286          *
287          * @method _uiSetShim
288          * @protected
289          * @param {boolean} enable If true, creates/renders the shim, if false, removes it.
290          */
291         _uiSetShim: function (enable) {
292             if (enable) {
293                 // Lazy creation
294                 if (this.get(VISIBLE)) {
295                     this._renderShim();
296                 } else {
297                     this._renderShimDeferred();
298                 }
300                 // Eagerly attach resize handlers
301                 //
302                 // Required because of Event stack behavior, commit ref: cd8dddc
303                 // Should be revisted after Ticket #2531067 is resolved.
304                 if (UA.ie == 6) {
305                     this._addShimResizeHandlers();
306                 }
307             } else {
308                 this._destroyShim();
309             }
310         },
312         /**
313          * Sets up change handlers for the visible attribute, to defer shim creation/rendering
314          * until the Widget is made visible.
315          *
316          * @method _renderShimDeferred
317          * @private
318          */
319         _renderShimDeferred : function() {
321             this._stackHandles[SHIM_DEFERRED] = this._stackHandles[SHIM_DEFERRED] || [];
323             var handles = this._stackHandles[SHIM_DEFERRED],
324                 createBeforeVisible = function(e) {
325                     if (e.newVal) {
326                         this._renderShim();
327                     }
328                 };
330             handles.push(this.on(VisibleChange, createBeforeVisible));
331             // Depending how how Ticket #2531067 is resolved, a reversal of
332             // commit ref: cd8dddc could lead to a more elagent solution, with
333             // the addition of this line here:
334             //
335             // handles.push(this.after(VisibleChange, this.sizeShim));
336         },
338         /**
339          * Sets up event listeners to resize the shim when the size of the Widget changes.
340          * <p>
341          * NOTE: This method is only used for IE6 currently, since IE6 doesn't support a way to
342          * resize the shim purely through CSS, when the Widget does not have an explicit width/height
343          * set.
344          * </p>
345          * @method _addShimResizeHandlers
346          * @private
347          */
348         _addShimResizeHandlers : function() {
350             this._stackHandles[SHIM_RESIZE] = this._stackHandles[SHIM_RESIZE] || [];
352             var sizeShim = this.sizeShim,
353                 handles = this._stackHandles[SHIM_RESIZE];
355             handles.push(this.after(VisibleChange, sizeShim));
356             handles.push(this.after(WidthChange, sizeShim));
357             handles.push(this.after(HeightChange, sizeShim));
358             handles.push(this.after(ContentUpdate, sizeShim));
359         },
361         /**
362          * Detaches any handles stored for the provided key
363          *
364          * @method _detachStackHandles
365          * @param String handleKey The key defining the group of handles which should be detached
366          * @private
367          */
368         _detachStackHandles : function(handleKey) {
369             var handles = this._stackHandles[handleKey],
370                 handle;
372             if (handles && handles.length > 0) {
373                 while((handle = handles.pop())) {
374                     handle.detach();
375                 }
376             }
377         },
379         /**
380          * Creates the shim element and adds it to the DOM
381          *
382          * @method _renderShim
383          * @private
384          */
385         _renderShim : function() {
386             var shimEl = this._shimNode,
387                 stackEl = this._stackNode;
389             if (!shimEl) {
390                 shimEl = this._shimNode = this._getShimTemplate();
391                 stackEl.insertBefore(shimEl, stackEl.get(FIRST_CHILD));
393                 this._detachStackHandles(SHIM_DEFERRED);
394                 this.sizeShim();
395             }
396         },
398         /**
399          * Removes the shim from the DOM, and detaches any related event
400          * listeners.
401          *
402          * @method _destroyShim
403          * @private
404          */
405         _destroyShim : function() {
406             if (this._shimNode) {
407                 this._shimNode.get(PARENT_NODE).removeChild(this._shimNode);
408                 this._shimNode = null;
410                 this._detachStackHandles(SHIM_DEFERRED);
411                 this._detachStackHandles(SHIM_RESIZE);
412             }
413         },
415         /**
416          * For IE6, synchronizes the size and position of iframe shim to that of
417          * Widget bounding box which it is protecting. For all other browsers,
418          * this method does not do anything.
419          *
420          * @method sizeShim
421          */
422         sizeShim: function () {
423             var shim = this._shimNode,
424                 node = this._stackNode;
426             if (shim && UA.ie === 6 && this.get(VISIBLE)) {
427                 shim.setStyle(WIDTH, node.get(OFFSET_WIDTH) + PX);
428                 shim.setStyle(HEIGHT, node.get(OFFSET_HEIGHT) + PX);
429             }
430         },
432         /**
433          * Creates a cloned shim node, using the SHIM_TEMPLATE html template, for use on a new instance.
434          *
435          * @method _getShimTemplate
436          * @private
437          * @return {Node} node A new shim Node instance.
438          */
439         _getShimTemplate : function() {
440             return Node.create(Stack.SHIM_TEMPLATE, this._stackNode.get(OWNER_DOCUMENT));
441         }
442     };
444     Y.WidgetStack = Stack;
447 }, '3.13.0', {"requires": ["base-build", "widget"], "skinnable": true});