~smagoun/whoopsie/whoopsie-lp1017637

« back to all changes in this revision

Viewing changes to backend/stats/static/js/yui/build/widget-modality/widget-modality.js

  • Committer: Evan Dandrea
  • Date: 2012-05-09 05:53:45 UTC
  • Revision ID: evan.dandrea@canonical.com-20120509055345-z2j41tmcbf4as5uf
The backend now lives in lp:daisy and the website (errors.ubuntu.com) now lives in lp:errors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.5.0 (build 5089)
3
 
Copyright 2012 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('widget-modality', function(Y) {
8
 
 
9
 
/**
10
 
 * Provides modality support for Widgets, though an extension
11
 
 *
12
 
 * @module widget-modality
13
 
 */
14
 
 
15
 
var WIDGET       = 'widget',
16
 
    RENDER_UI    = 'renderUI',
17
 
    BIND_UI      = 'bindUI',
18
 
    SYNC_UI      = 'syncUI',
19
 
    BOUNDING_BOX = 'boundingBox',
20
 
    CONTENT_BOX  = 'contentBox',
21
 
    VISIBLE      = 'visible',
22
 
    Z_INDEX      = 'zIndex',
23
 
    CHANGE       = 'Change',
24
 
    isBoolean    = Y.Lang.isBoolean,
25
 
    getCN        = Y.ClassNameManager.getClassName,
26
 
    MaskShow     = "maskShow",
27
 
    MaskHide     = "maskHide",
28
 
    ClickOutside = "clickoutside",
29
 
    FocusOutside = "focusoutside",
30
 
 
31
 
    supportsPosFixed = (function(){
32
 
 
33
 
        /*! IS_POSITION_FIXED_SUPPORTED - Juriy Zaytsev (kangax) - http://yura.thinkweb2.com/cft/ */
34
 
 
35
 
        var doc         = Y.config.doc,
36
 
            isSupported = null,
37
 
            el, root;
38
 
 
39
 
        if (doc.createElement) {
40
 
            el = doc.createElement('div');
41
 
            if (el && el.style) {
42
 
                el.style.position = 'fixed';
43
 
                el.style.top = '10px';
44
 
                root = doc.body;
45
 
                if (root && root.appendChild && root.removeChild) {
46
 
                    root.appendChild(el);
47
 
                    isSupported = (el.offsetTop === 10);
48
 
                    root.removeChild(el);
49
 
                }
50
 
            }
51
 
        }
52
 
 
53
 
        return isSupported;
54
 
    }());
55
 
 
56
 
    /**
57
 
     * Widget extension, which can be used to add modality support to the base Widget class,
58
 
     * through the Base.create method.
59
 
     *
60
 
     * @class WidgetModality
61
 
     * @param {Object} config User configuration object
62
 
     */
63
 
    function WidgetModal(config) {}
64
 
 
65
 
    var MODAL           = 'modal',
66
 
        MASK            = 'mask',
67
 
        MODAL_CLASSES   = {
68
 
            modal   : getCN(WIDGET, MODAL),
69
 
            mask    : getCN(WIDGET, MASK)
70
 
        };
71
 
 
72
 
    /**
73
 
    * Static property used to define the default attribute
74
 
    * configuration introduced by WidgetModality.
75
 
    *
76
 
    * @property ATTRS
77
 
    * @static
78
 
    * @type Object
79
 
    */
80
 
    WidgetModal.ATTRS = {
81
 
            /**
82
 
             * @attribute maskNode
83
 
             * @type Y.Node
84
 
             *
85
 
             * @description Returns a Y.Node instance of the node being used as the mask.
86
 
             */
87
 
            maskNode : {
88
 
                getter      : '_getMaskNode',
89
 
                readOnly    : true
90
 
            },
91
 
 
92
 
 
93
 
            /**
94
 
             * @attribute modal
95
 
             * @type boolean
96
 
             *
97
 
             * @description Whether the widget should be modal or not.
98
 
             */
99
 
            modal: {
100
 
                value:false,
101
 
                validator: isBoolean
102
 
            },
103
 
 
104
 
            /**
105
 
             * @attribute focusOn
106
 
             * @type array
107
 
             *
108
 
             * @description An array of objects corresponding to the nodes and events that will trigger a re-focus back on the widget.
109
 
             * The implementer can supply an array of objects, with each object having the following properties:
110
 
             * <p>eventName: (string, required): The eventName to listen to.</p>
111
 
             * <p>node: (Y.Node, optional): The Y.Node that will fire the event (defaults to the boundingBox of the widget)</p>
112
 
             * <p>By default, this attribute consists of two objects which will cause the widget to re-focus if anything
113
 
             * outside the widget is clicked on or focussed upon.</p>
114
 
             */
115
 
            focusOn: {
116
 
                valueFn: function() {
117
 
                    return [
118
 
                        {
119
 
                            // node: this.get(BOUNDING_BOX),
120
 
                            eventName: ClickOutside
121
 
                        },
122
 
                        {
123
 
                            //node: this.get(BOUNDING_BOX),
124
 
                            eventName: FocusOutside
125
 
                        }
126
 
                    ];
127
 
                },
128
 
 
129
 
                validator: Y.Lang.isArray
130
 
            }
131
 
 
132
 
    };
133
 
 
134
 
 
135
 
    WidgetModal.CLASSES = MODAL_CLASSES;
136
 
 
137
 
 
138
 
    /**
139
 
     * Returns the mask if it exists on the page - otherwise creates a mask. There's only
140
 
     * one mask on a page at a given time.
141
 
     * <p>
142
 
     * This method in invoked internally by the getter of the maskNode ATTR.
143
 
     * </p>
144
 
     * @method _GET_MASK
145
 
     * @static
146
 
     */
147
 
    WidgetModal._GET_MASK = function() {
148
 
 
149
 
        var mask = Y.one(".yui3-widget-mask") || null,
150
 
        win = Y.one('window');
151
 
 
152
 
        if (mask) {
153
 
            return mask;
154
 
        }
155
 
        else {
156
 
 
157
 
            mask = Y.Node.create('<div></div>');
158
 
            mask.addClass(MODAL_CLASSES.mask);
159
 
            if (supportsPosFixed) {
160
 
                mask.setStyles({
161
 
                    position    : 'fixed',
162
 
                    width       : '100%',
163
 
                    height      : '100%',
164
 
                    top         : '0',
165
 
                    left        : '0',
166
 
                    display     : 'block'
167
 
                });
168
 
            }
169
 
            else {
170
 
                mask.setStyles({
171
 
                    position    : 'absolute',
172
 
                    width       : win.get('winWidth') +'px',
173
 
                    height      : win.get('winHeight') + 'px',
174
 
                    top         : '0',
175
 
                    left        : '0',
176
 
                    display     : 'block'
177
 
                });
178
 
            }
179
 
 
180
 
 
181
 
 
182
 
            return mask;
183
 
        }
184
 
 
185
 
    };
186
 
 
187
 
    /**
188
 
     * A stack of Y.Widget objects representing the current hierarchy of modal widgets presently displayed on the screen
189
 
     * @property STACK
190
 
     */
191
 
    WidgetModal.STACK = [];
192
 
 
193
 
 
194
 
    WidgetModal.prototype = {
195
 
 
196
 
        initializer: function () {
197
 
            Y.after(this._renderUIModal, this, RENDER_UI);
198
 
            Y.after(this._syncUIModal, this, SYNC_UI);
199
 
            Y.after(this._bindUIModal, this, BIND_UI);
200
 
        },
201
 
 
202
 
        destructor: function () {
203
 
            // Hack to remove this thing from the STACK.
204
 
            this._uiSetHostVisibleModal(false);
205
 
        },
206
 
 
207
 
        // *** Instance Members *** //
208
 
 
209
 
        _uiHandlesModal: null,
210
 
 
211
 
 
212
 
        /**
213
 
         * Adds modal class to the bounding box of the widget
214
 
         * <p>
215
 
         * This method in invoked after renderUI is invoked for the Widget class
216
 
         * using YUI's aop infrastructure.
217
 
         * </p>
218
 
         * @method _renderUIModal
219
 
         * @protected
220
 
         */
221
 
        _renderUIModal : function () {
222
 
 
223
 
            var bb = this.get(BOUNDING_BOX);
224
 
                //cb = this.get(CONTENT_BOX);
225
 
 
226
 
            //this makes the content box content appear over the mask
227
 
            // cb.setStyles({
228
 
            //     position: ""
229
 
            // });
230
 
 
231
 
            this._repositionMask(this);
232
 
            bb.addClass(MODAL_CLASSES.modal);
233
 
 
234
 
        },
235
 
 
236
 
 
237
 
        /**
238
 
         * Hooks up methods to be executed when the widget's visibility or z-index changes
239
 
         * <p>
240
 
         * This method in invoked after bindUI is invoked for the Widget class
241
 
         * using YUI's aop infrastructure.
242
 
         * </p>
243
 
         * @method _bindUIModal
244
 
         * @protected
245
 
         */
246
 
        _bindUIModal : function () {
247
 
 
248
 
            this.after(VISIBLE+CHANGE, this._afterHostVisibleChangeModal);
249
 
            this.after(Z_INDEX+CHANGE, this._afterHostZIndexChangeModal);
250
 
            this.after("focusOnChange", this._afterFocusOnChange);
251
 
 
252
 
            // Re-align the mask in the viewport if `position: fixed;` is not
253
 
            // supported. iOS < 5 and Android < 3 don't actually support it even
254
 
            // though they both pass the feature test; the UA sniff is here to
255
 
            // account for that. Ideally this should be replaced with a better
256
 
            // feature test.
257
 
            if (!supportsPosFixed || Y.UA.ios < 5 || Y.UA.android < 3) {
258
 
                Y.one('win').on('scroll', this._resyncMask, this);
259
 
            }
260
 
        },
261
 
 
262
 
        /**
263
 
         * Syncs the mask with the widget's current state, namely the visibility and z-index of the widget
264
 
         * <p>
265
 
         * This method in invoked after syncUI is invoked for the Widget class
266
 
         * using YUI's aop infrastructure.
267
 
         * </p>
268
 
         * @method _syncUIModal
269
 
         * @protected
270
 
         */
271
 
        _syncUIModal : function () {
272
 
 
273
 
            //var host = this.get(HOST);
274
 
 
275
 
            this._uiSetHostVisibleModal(this.get(VISIBLE));
276
 
            this._uiSetHostZIndexModal(this.get(Z_INDEX));
277
 
 
278
 
        },
279
 
 
280
 
        /**
281
 
         * Provides mouse and tab focus to the widget's bounding box.
282
 
         *
283
 
         * @method _focus
284
 
         */
285
 
        _focus : function (e) {
286
 
 
287
 
            var bb = this.get(BOUNDING_BOX),
288
 
            oldTI = bb.get('tabIndex');
289
 
 
290
 
            bb.set('tabIndex', oldTI >= 0 ? oldTI : 0);
291
 
            this.focus();
292
 
        },
293
 
        /**
294
 
         * Blurs the widget.
295
 
         *
296
 
         * @method _blur
297
 
         */
298
 
        _blur : function () {
299
 
 
300
 
            this.blur();
301
 
        },
302
 
 
303
 
        /**
304
 
         * Returns the Y.Node instance of the maskNode
305
 
         *
306
 
         * @method _getMaskNode
307
 
         * @return {Node} The Y.Node instance of the mask, as returned from WidgetModal._GET_MASK
308
 
         */
309
 
        _getMaskNode : function () {
310
 
 
311
 
            return WidgetModal._GET_MASK();
312
 
        },
313
 
 
314
 
        /**
315
 
         * Performs events attaching/detaching, stack shifting and mask repositioning based on the visibility of the widget
316
 
         *
317
 
         * @method _uiSetHostVisibleModal
318
 
         * @param {boolean} Whether the widget is visible or not
319
 
         */
320
 
        _uiSetHostVisibleModal : function (visible) {
321
 
            var stack    = WidgetModal.STACK,
322
 
                maskNode = this.get('maskNode'),
323
 
                isModal  = this.get('modal'),
324
 
                topModal, index;
325
 
 
326
 
            if (visible) {
327
 
 
328
 
                Y.Array.each(stack, function(modal){
329
 
                    modal._detachUIHandlesModal();
330
 
                    modal._blur();
331
 
                });
332
 
 
333
 
                // push on top of stack
334
 
                stack.unshift(this);
335
 
 
336
 
                this._repositionMask(this);
337
 
                this._uiSetHostZIndexModal(this.get(Z_INDEX));
338
 
 
339
 
                if (isModal) {
340
 
                    maskNode.show();
341
 
                    Y.later(1, this, '_attachUIHandlesModal');
342
 
                    this._focus();
343
 
                }
344
 
 
345
 
 
346
 
            } else {
347
 
 
348
 
                index = Y.Array.indexOf(stack, this);
349
 
                if (index >= 0) {
350
 
                    // Remove modal widget from global stack.
351
 
                    stack.splice(index, 1);
352
 
                }
353
 
 
354
 
                this._detachUIHandlesModal();
355
 
                this._blur();
356
 
 
357
 
                if (stack.length) {
358
 
                    topModal = stack[0];
359
 
                    this._repositionMask(topModal);
360
 
                    //topModal._attachUIHandlesModal();
361
 
                    topModal._uiSetHostZIndexModal(topModal.get(Z_INDEX));
362
 
 
363
 
                    if (topModal.get('modal')) {
364
 
                        //topModal._attachUIHandlesModal();
365
 
                        Y.later(1, topModal, '_attachUIHandlesModal');
366
 
                        topModal._focus();
367
 
                    }
368
 
 
369
 
                } else {
370
 
 
371
 
                    if (maskNode.getStyle('display') === 'block') {
372
 
                        maskNode.hide();
373
 
                    }
374
 
 
375
 
                }
376
 
 
377
 
            }
378
 
        },
379
 
 
380
 
        /**
381
 
         * Sets the z-index of the mask node.
382
 
         *
383
 
         * @method _uiSetHostZIndexModal
384
 
         * @param {Number} Z-Index of the widget
385
 
         */
386
 
        _uiSetHostZIndexModal : function (zIndex) {
387
 
 
388
 
            if (this.get('modal')) {
389
 
                this.get('maskNode').setStyle(Z_INDEX, zIndex || 0);
390
 
            }
391
 
 
392
 
        },
393
 
 
394
 
        /**
395
 
         * Attaches UI Listeners for "clickoutside" and "focusoutside" on the widget. When these events occur, and the widget is modal, focus is shifted back onto the widget.
396
 
         *
397
 
         * @method _attachUIHandlesModal
398
 
         */
399
 
        _attachUIHandlesModal : function () {
400
 
 
401
 
            if (this._uiHandlesModal || WidgetModal.STACK[0] !== this) {
402
 
                // Quit early if we have ui handles, or if we not at the top
403
 
                // of the global stack.
404
 
                return;
405
 
            }
406
 
 
407
 
            var bb          = this.get(BOUNDING_BOX),
408
 
                maskNode    = this.get('maskNode'),
409
 
                focusOn     = this.get('focusOn'),
410
 
                focus       = Y.bind(this._focus, this),
411
 
                uiHandles   = [],
412
 
                i, len, o;
413
 
 
414
 
            for (i = 0, len = focusOn.length; i < len; i++) {
415
 
 
416
 
                o = {};
417
 
                o.node = focusOn[i].node;
418
 
                o.ev = focusOn[i].eventName;
419
 
                o.keyCode = focusOn[i].keyCode;
420
 
 
421
 
                //no keycode or node defined
422
 
                if (!o.node && !o.keyCode && o.ev) {
423
 
                    uiHandles.push(bb.on(o.ev, focus));
424
 
                }
425
 
 
426
 
                //node defined, no keycode (not a keypress)
427
 
                else if (o.node && !o.keyCode && o.ev) {
428
 
                    uiHandles.push(o.node.on(o.ev, focus));
429
 
                }
430
 
 
431
 
                //node defined, keycode defined, event defined (its a key press)
432
 
                else if (o.node && o.keyCode && o.ev) {
433
 
                    uiHandles.push(o.node.on(o.ev, focus, o.keyCode));
434
 
                }
435
 
 
436
 
                else {
437
 
                    Y.Log('focusOn ATTR Error: The event with name "'+o.ev+'" could not be attached.');
438
 
                }
439
 
 
440
 
            }
441
 
 
442
 
            if ( ! supportsPosFixed) {
443
 
                uiHandles.push(Y.one('win').on('scroll', Y.bind(function(e){
444
 
                    maskNode.setStyle('top', maskNode.get('docScrollY'));
445
 
                }, this)));
446
 
            }
447
 
 
448
 
            this._uiHandlesModal = uiHandles;
449
 
        },
450
 
 
451
 
        /**
452
 
         * Detaches all UI Listeners that were set in _attachUIHandlesModal from the widget.
453
 
         *
454
 
         * @method _detachUIHandlesModal
455
 
         */
456
 
        _detachUIHandlesModal : function () {
457
 
            Y.each(this._uiHandlesModal, function(h){
458
 
                h.detach();
459
 
            });
460
 
            this._uiHandlesModal = null;
461
 
        },
462
 
 
463
 
        /**
464
 
         * Default function that is called when visibility is changed on the widget.
465
 
         *
466
 
         * @method _afterHostVisibleChangeModal
467
 
         * @param {EventFacade} e The event facade of the change
468
 
         */
469
 
        _afterHostVisibleChangeModal : function (e) {
470
 
 
471
 
            this._uiSetHostVisibleModal(e.newVal);
472
 
        },
473
 
 
474
 
        /**
475
 
         * Default function that is called when z-index is changed on the widget.
476
 
         *
477
 
         * @method _afterHostZIndexChangeModal
478
 
         * @param {EventFacade} e The event facade of the change
479
 
         */
480
 
        _afterHostZIndexChangeModal : function (e) {
481
 
 
482
 
            this._uiSetHostZIndexModal(e.newVal);
483
 
        },
484
 
 
485
 
        /**
486
 
         * Returns a boolean representing whether the current widget is in a "nested modality" state.
487
 
         * This is done by checking the number of widgets currently on the stack.
488
 
         *
489
 
         * @method isNested
490
 
         * @public
491
 
         */
492
 
        isNested: function() {
493
 
            var length = WidgetModal.STACK.length,
494
 
            retval = (length > 1) ? true : false;
495
 
            return retval;
496
 
        },
497
 
 
498
 
        /**
499
 
         * Repositions the mask in the DOM for nested modality cases.
500
 
         *
501
 
         * @method _repositionMask
502
 
         * @param {Widget} nextElem The Y.Widget instance that will be visible in the stack once the current widget is closed.
503
 
         */
504
 
        _repositionMask: function(nextElem) {
505
 
 
506
 
            var currentModal = this.get('modal'),
507
 
                nextModal    = nextElem.get('modal'),
508
 
                maskNode     = this.get('maskNode'),
509
 
                bb, bbParent;
510
 
 
511
 
            //if this is modal and host is not modal
512
 
            if (currentModal && !nextModal) {
513
 
                //leave the mask where it is, since the host is not modal.
514
 
                maskNode.remove();
515
 
                this.fire(MaskHide);
516
 
            }
517
 
 
518
 
            //if the main widget is not modal but the host is modal, or both of them are modal
519
 
            else if ((!currentModal && nextModal) || (currentModal && nextModal)) {
520
 
 
521
 
                //then remove the mask off DOM, reposition it, and reinsert it into the DOM
522
 
                maskNode.remove();
523
 
                this.fire(MaskHide);
524
 
                bb = nextElem.get(BOUNDING_BOX);
525
 
                bbParent = bb.get('parentNode') || Y.one('body');
526
 
                bbParent.insert(maskNode, bbParent.get('firstChild'));
527
 
                this.fire(MaskShow);
528
 
            }
529
 
 
530
 
        },
531
 
 
532
 
        /**
533
 
         * Resyncs the mask in the viewport for browsers that don't support fixed positioning
534
 
         *
535
 
         * @method _resyncMask
536
 
         * @param {Y.Widget} nextElem The Y.Widget instance that will be visible in the stack once the current widget is closed.
537
 
         * @private
538
 
         */
539
 
        _resyncMask: function (e) {
540
 
            var o       = e.currentTarget,
541
 
                offsetX = o.get('docScrollX'),
542
 
                offsetY = o.get('docScrollY'),
543
 
                w       = o.get('innerWidth') || o.get('winWidth'),
544
 
                h       = o.get('innerHeight') || o.get('winHeight'),
545
 
                mask    = this.get('maskNode');
546
 
 
547
 
            mask.setStyles({
548
 
                "top": offsetY + "px",
549
 
                "left": offsetX + "px",
550
 
                "width": w + 'px',
551
 
                "height": h + 'px'
552
 
            });
553
 
        },
554
 
 
555
 
        /**
556
 
         * Default function called when focusOn Attribute is changed. Remove existing listeners and create new listeners.
557
 
         *
558
 
         * @method _afterFocusOnChange
559
 
         */
560
 
        _afterFocusOnChange : function(e) {
561
 
            this._detachUIHandlesModal();
562
 
 
563
 
            if (this.get(VISIBLE)) {
564
 
                this._attachUIHandlesModal();
565
 
            }
566
 
        }
567
 
    };
568
 
 
569
 
    Y.WidgetModality = WidgetModal;
570
 
 
571
 
 
572
 
 
573
 
}, '3.5.0' ,{requires:['base-build', 'event-outside', 'widget'], skinnable:true});