Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('node-scroll-info', function (Y, NAME) {
2
 
3
/*jshint onevar:false */
4
 
5
/**
6
Provides the ScrollInfo Node plugin, which exposes convenient events and methods
7
related to scrolling.
8
 
9
@module node-scroll-info
10
@since 3.7.0
11
**/
12
 
13
/**
14
Provides convenient events and methods related to scrolling. This could be used,
15
for example, to implement infinite scrolling, or to lazy-load content based on
16
the current scroll position.
17
 
18
### Example
19
 
20
    var body = Y.one('body');
21
 
22
    body.plug(Y.Plugin.ScrollInfo);
23
 
24
    body.scrollInfo.on('scrollToBottom', function (e) {
25
        // Load more content when the user scrolls to the bottom of the page.
26
    });
27
 
28
@class Plugin.ScrollInfo
29
@extends Plugin.Base
30
@since 3.7.0
31
**/
32
 
33
var doc = Y.config.doc,
34
    win = Y.config.win;
35
 
36
/**
37
Fired when the user scrolls within the host node.
38
 
39
This event (like all scroll events exposed by ScrollInfo) is throttled and fired
40
only after the number of milliseconds specified by the `scrollDelay` attribute
41
have passed in order to prevent thrashing.
42
 
43
This event passes along the event facade for the standard DOM `scroll` event and
44
mixes in the following additional properties.
45
 
46
@event scroll
47
@param {Boolean} atBottom Whether the current scroll position is at the bottom
48
    of the scrollable region.
49
@param {Boolean} atLeft Whether the current scroll position is at the extreme
50
    left of the scrollable region.
51
@param {Boolean} atRight Whether the current scroll position is at the extreme
52
    right of the scrollable region.
53
@param {Boolean} atTop Whether the current scroll position is at the top of the
54
    scrollable region.
55
@param {Boolean} isScrollDown `true` if the user scrolled down.
56
@param {Boolean} isScrollLeft `true` if the user scrolled left.
57
@param {Boolean} isScrollRight `true` if the user scrolled right.
58
@param {Boolean} isScrollUp `true` if the user scrolled up.
59
@param {Number} scrollBottom Y value of the bottom-most onscreen pixel of the
60
    scrollable region.
61
@param {Number} scrollHeight Total height in pixels of the scrollable region,
62
    including offscreen pixels.
63
@param {Number} scrollLeft X value of the left-most onscreen pixel of the
64
    scrollable region.
65
@param {Number} scrollRight X value of the right-most onscreen pixel of the
66
    scrollable region.
67
@param {Number} scrollTop Y value of the top-most onscreen pixel of the
68
    scrollable region.
69
@param {Number} scrollWidth Total width in pixels of the scrollable region,
70
    including offscreen pixels.
71
@see scrollDelay
72
@see scrollMargin
73
**/
74
var EVT_SCROLL = 'scroll',
75
 
76
    /**
77
    Fired when the user scrolls down within the host node.
78
 
79
    This event provides the same event facade as the `scroll` event. See that
80
    event for details.
81
 
82
    @event scrollDown
83
    @see scroll
84
    **/
85
    EVT_SCROLL_DOWN = 'scrollDown',
86
 
87
    /**
88
    Fired when the user scrolls left within the host node.
89
 
90
    This event provides the same event facade as the `scroll` event. See that
91
    event for details.
92
 
93
    @event scrollLeft
94
    @see scroll
95
    **/
96
    EVT_SCROLL_LEFT = 'scrollLeft',
97
 
98
    /**
99
    Fired when the user scrolls right within the host node.
100
 
101
    This event provides the same event facade as the `scroll` event. See that
102
    event for details.
103
 
104
    @event scrollRight
105
    @see scroll
106
    **/
107
    EVT_SCROLL_RIGHT = 'scrollRight',
108
 
109
    /**
110
    Fired when the user scrolls up within the host node.
111
 
112
    This event provides the same event facade as the `scroll` event. See that
113
    event for details.
114
 
115
    @event scrollUp
116
    @see scroll
117
    **/
118
    EVT_SCROLL_UP = 'scrollUp',
119
 
120
    /**
121
    Fired when the user scrolls to the bottom of the scrollable region within
122
    the host node.
123
 
124
    This event provides the same event facade as the `scroll` event. See that
125
    event for details.
126
 
127
    @event scrollToBottom
128
    @see scroll
129
    **/
130
    EVT_SCROLL_TO_BOTTOM = 'scrollToBottom',
131
 
132
    /**
133
    Fired when the user scrolls to the extreme left of the scrollable region
134
    within the host node.
135
 
136
    This event provides the same event facade as the `scroll` event. See that
137
    event for details.
138
 
139
    @event scrollToLeft
140
    @see scroll
141
    **/
142
    EVT_SCROLL_TO_LEFT = 'scrollToLeft',
143
 
144
    /**
145
    Fired when the user scrolls to the extreme right of the scrollable region
146
    within the host node.
147
 
148
    This event provides the same event facade as the `scroll` event. See that
149
    event for details.
150
 
151
    @event scrollToRight
152
    @see scroll
153
    **/
154
    EVT_SCROLL_TO_RIGHT = 'scrollToRight',
155
 
156
    /**
157
    Fired when the user scrolls to the top of the scrollable region within the
158
    host node.
159
 
160
    This event provides the same event facade as the `scroll` event. See that
161
    event for details.
162
 
163
    @event scrollToTop
164
    @see scroll
165
    **/
166
    EVT_SCROLL_TO_TOP = 'scrollToTop';
167
 
168
Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
169
    // -- Protected Properties -------------------------------------------------
170
 
171
    /**
172
    Height of the visible region of the host node in pixels. If the host node is
173
    the body, this will be the same as `_winHeight`.
174
 
175
    @property {Number} _height
176
    @protected
177
    **/
178
 
179
    /**
180
    Whether or not the host node is the `<body>` element.
181
 
182
    @property {Boolean} _hostIsBody
183
    @protected
184
    **/
185
 
186
    /**
187
    Width of the visible region of the host node in pixels. If the host node is
188
    the body, this will be the same as `_winWidth`.
189
 
190
    @property {Number} _width
191
    @protected
192
    **/
193
 
194
    /**
195
    Height of the viewport in pixels.
196
 
197
    @property {Number} _winHeight
198
    @protected
199
    **/
200
 
201
    /**
202
    Width of the viewport in pixels.
203
 
204
    @property {Number} _winWidth
205
    @protected
206
    **/
207
 
208
    // -- Lifecycle Methods ----------------------------------------------------
209
    initializer: function (config) {
210
        // Cache for quicker lookups in the critical path.
211
        this._host                  = config.host;
212
        this._hostIsBody            = this._host.get('nodeName').toLowerCase() === 'body';
213
        this._scrollDelay           = this.get('scrollDelay');
214
        this._scrollMargin          = this.get('scrollMargin');
215
        this._scrollNode            = this._getScrollNode();
216
 
217
        this.refreshDimensions();
218
 
219
        this._lastScroll = this.getScrollInfo();
220
 
221
        this._bind();
222
    },
223
 
224
    destructor: function () {
225
        new Y.EventHandle(this._events).detach();
226
        this._events = null;
227
    },
228
 
229
    // -- Public Methods -------------------------------------------------------
230
 
231
    /**
232
    Returns a NodeList containing all offscreen nodes inside the host node that
233
    match the given CSS selector. An offscreen node is any node that is entirely
234
    outside the visible (onscreen) region of the host node based on the current
235
    scroll location.
236
 
237
    @method getOffscreenNodes
238
    @param {String} [selector] CSS selector. If omitted, all offscreen nodes
239
        will be returned.
240
    @param {Number} [margin] Additional margin in pixels beyond the actual
241
        onscreen region that should be considered "onscreen" for the purposes of
242
        this query. Defaults to the value of the `scrollMargin` attribute.
243
    @return {NodeList} Offscreen nodes matching _selector_.
244
    @see scrollMargin
245
    **/
246
    getOffscreenNodes: function (selector, margin) {
247
        if (typeof margin === 'undefined') {
248
            margin = this._scrollMargin;
249
        }
250
 
251
        var elements = Y.Selector.query(selector || '*', this._host._node);
252
 
253
        return new Y.NodeList(Y.Array.filter(elements, function (el) {
254
            return !this._isElementOnscreen(el, margin);
255
        }, this));
256
    },
257
 
258
    /**
259
    Returns a NodeList containing all onscreen nodes inside the host node that
260
    match the given CSS selector. An onscreen node is any node that is fully or
261
    partially within the visible (onscreen) region of the host node based on the
262
    current scroll location.
263
 
264
    @method getOnscreenNodes
265
    @param {String} [selector] CSS selector. If omitted, all onscreen nodes will
266
        be returned.
267
    @param {Number} [margin] Additional margin in pixels beyond the actual
268
        onscreen region that should be considered "onscreen" for the purposes of
269
        this query. Defaults to the value of the `scrollMargin` attribute.
270
    @return {NodeList} Onscreen nodes matching _selector_.
271
    @see scrollMargin
272
    **/
273
    getOnscreenNodes: function (selector, margin) {
274
        if (typeof margin === 'undefined') {
275
            margin = this._scrollMargin;
276
        }
277
 
278
        var elements = Y.Selector.query(selector || '*', this._host._node);
279
 
280
        return new Y.NodeList(Y.Array.filter(elements, function (el) {
281
            return this._isElementOnscreen(el, margin);
282
        }, this));
283
    },
284
 
285
    /**
286
    Returns an object hash containing information about the current scroll
287
    position of the host node. This is the same information that's mixed into
288
    the event facade of the `scroll` event and other scroll-related events.
289
 
290
    @method getScrollInfo
291
    @return {Object} Object hash containing information about the current scroll
292
        position. See the `scroll` event for details on what properties this
293
        object contains.
294
    @see scroll
295
    **/
296
    getScrollInfo: function () {
297
        var domNode    = this._scrollNode,
298
            lastScroll = this._lastScroll,
299
            margin     = this._scrollMargin,
300
 
301
            scrollLeft   = domNode.scrollLeft,
302
            scrollHeight = domNode.scrollHeight,
303
            scrollTop    = domNode.scrollTop,
304
            scrollWidth  = domNode.scrollWidth,
305
 
306
            scrollBottom = scrollTop + this._height,
307
            scrollRight  = scrollLeft + this._width;
308
 
309
        return {
310
            atBottom: scrollBottom > (scrollHeight - margin),
311
            atLeft  : scrollLeft < margin,
312
            atRight : scrollRight > (scrollWidth - margin),
313
            atTop   : scrollTop < margin,
314
 
315
            isScrollDown : lastScroll && scrollTop > lastScroll.scrollTop,
316
            isScrollLeft : lastScroll && scrollLeft < lastScroll.scrollLeft,
317
            isScrollRight: lastScroll && scrollLeft > lastScroll.scrollLeft,
318
            isScrollUp   : lastScroll && scrollTop < lastScroll.scrollTop,
319
 
320
            scrollBottom: scrollBottom,
321
            scrollHeight: scrollHeight,
322
            scrollLeft  : scrollLeft,
323
            scrollRight : scrollRight,
324
            scrollTop   : scrollTop,
325
            scrollWidth : scrollWidth
326
        };
327
    },
328
 
329
    /**
330
    Returns `true` if _node_ is at least partially onscreen within the host
331
    node, `false` otherwise.
332
 
333
    @method isNodeOnscreen
334
    @param {HTMLElement|Node|String} node Node or selector to check.
335
    @param {Number} [margin] Additional margin in pixels beyond the actual
336
        onscreen region that should be considered "onscreen" for the purposes of
337
        this query. Defaults to the value of the `scrollMargin` attribute.
338
    @return {Boolean} `true` if _node_ is at least partially onscreen within the
339
        host node, `false` otherwise.
340
    @since 3.11.0
341
    **/
342
    isNodeOnscreen: function (node, margin) {
343
        node = Y.one(node);
344
        return !!(node && this._isElementOnscreen(node._node, margin));
345
    },
346
 
347
    /**
348
    Refreshes cached position, height, and width dimensions for the host node.
349
    If the host node is the body, then the viewport height and width will be
350
    used.
351
 
352
    This info is cached to improve performance during scroll events, since it's
353
    expensive to touch the DOM for these values. Dimensions are automatically
354
    refreshed whenever the browser is resized, but if you change the dimensions
355
    or position of the host node in JS, you may need to call
356
    `refreshDimensions()` manually to cache the new dimensions.
357
 
358
    @method refreshDimensions
359
    **/
360
    refreshDimensions: function () {
361
        var docEl = doc.documentElement;
362
 
363
        // On iOS devices and on Chrome for Android,
364
        // documentElement.clientHeight/Width aren't reliable, but
365
        // window.innerHeight/Width are. The dom-screen module's viewport size
366
        // methods don't account for this, which is why we do it here.
367
        if (Y.UA.ios || (Y.UA.android && Y.UA.chrome)) {
368
            this._winHeight = win.innerHeight;
369
            this._winWidth  = win.innerWidth;
370
        } else {
371
            this._winHeight = docEl.clientHeight;
372
            this._winWidth  = docEl.clientWidth;
373
        }
374
 
375
        if (this._hostIsBody) {
376
            this._height = this._winHeight;
377
            this._width  = this._winWidth;
378
        } else {
379
            this._height = this._scrollNode.clientHeight;
380
            this._width  = this._scrollNode.clientWidth;
381
        }
382
 
383
        this._refreshHostBoundingRect();
384
    },
385
 
386
    // -- Protected Methods ----------------------------------------------------
387
 
388
    /**
389
    Binds event handlers.
390
 
391
    @method _bind
392
    @protected
393
    **/
394
    _bind: function () {
395
        var winNode = Y.one('win');
396
 
397
        this._events = [
398
            this.after({
399
                scrollDelayChange : this._afterScrollDelayChange,
400
                scrollMarginChange: this._afterScrollMarginChange
401
            }),
402
 
403
            winNode.on('windowresize', this._afterResize, this)
404
        ];
405
 
406
        // If the host node is the body, listen for the scroll event on the
407
        // window, since <body> doesn't have a scroll event.
408
        if (this._hostIsBody) {
409
            this._events.push(winNode.after('scroll', this._afterHostScroll, this));
410
        } else {
411
            // The host node is not the body, but we still need to listen for
412
            // window scroll events so we can determine whether nodes are
413
            // onscreen.
414
            this._events.push(
415
                winNode.after('scroll', this._afterWindowScroll, this),
416
                this._host.after('scroll', this._afterHostScroll, this)
417
            );
418
        }
419
    },
420
 
421
    /**
422
    Returns the DOM node that should be used to lookup scroll coordinates. In
423
    some browsers, the `<body>` element doesn't return scroll coordinates, and
424
    the documentElement must be used instead; this method takes care of
425
    determining which node should be used.
426
 
427
    @method _getScrollNode
428
    @return {HTMLElement} DOM node.
429
    @protected
430
    **/
431
    _getScrollNode: function () {
432
        // WebKit returns scroll coordinates on the body element, but other
433
        // browsers don't, so we have to use the documentElement.
434
        return this._hostIsBody && !Y.UA.webkit ? doc.documentElement :
435
                Y.Node.getDOMNode(this._host);
436
    },
437
 
438
    /**
439
    Underlying element-based implementation for `isNodeOnscreen()`.
440
 
441
    @method _isElementOnscreen
442
    @param {HTMLElement} el HTML element.
443
    @param {Number} [margin] Additional margin in pixels beyond the actual
444
        onscreen region that should be considered "onscreen" for the purposes of
445
        this query. Defaults to the value of the `scrollMargin` attribute.
446
    @return {Boolean} `true` if _el_ is at least partially onscreen within the
447
        host node, `false` otherwise.
448
    @protected
449
    @since 3.11.0
450
    **/
451
    _isElementOnscreen: function (el, margin) {
452
        var hostRect = this._hostRect,
453
            rect     = el.getBoundingClientRect();
454
 
455
        if (typeof margin === 'undefined') {
456
            margin = this._scrollMargin;
457
        }
458
 
459
        // Determine whether any part of _el_ is within the visible region of
460
        // the host element or the specified margin around the visible region of
461
        // the host element.
462
        return !(rect.top > hostRect.bottom + margin
463
                    || rect.bottom < hostRect.top - margin
464
                    || rect.right < hostRect.left - margin
465
                    || rect.left > hostRect.right + margin);
466
    },
467
 
468
    /**
469
    Caches the bounding rect of the host node.
470
 
471
    If the host node is the body, the bounding rect will be faked to represent
472
    the dimensions of the viewport, since the actual body dimensions may extend
473
    beyond the viewport and we only care about the visible region.
474
 
475
    @method _refreshHostBoundingRect
476
    @protected
477
    **/
478
    _refreshHostBoundingRect: function () {
479
        var winHeight = this._winHeight,
480
            winWidth  = this._winWidth,
481
 
482
            hostRect;
483
 
484
        if (this._hostIsBody) {
485
            hostRect = {
486
                bottom: winHeight,
487
                height: winHeight,
488
                left  : 0,
489
                right : winWidth,
490
                top   : 0,
491
                width : winWidth
492
            };
493
 
494
            this._isHostOnscreen = true;
495
        } else {
496
            hostRect = this._scrollNode.getBoundingClientRect();
497
        }
498
 
499
        this._hostRect = hostRect;
500
    },
501
 
502
    /**
503
    Mixes detailed scroll information into the given DOM `scroll` event facade
504
    and fires appropriate local events.
505
 
506
    @method _triggerScroll
507
    @param {EventFacade} e Event facade from the DOM `scroll` event.
508
    @protected
509
    **/
510
    _triggerScroll: function (e) {
511
        var info       = this.getScrollInfo(),
512
            facade     = Y.merge(e, info),
513
            lastScroll = this._lastScroll;
514
 
515
        this._lastScroll = info;
516
 
517
        this.fire(EVT_SCROLL, facade);
518
 
519
        if (info.isScrollLeft) {
520
            this.fire(EVT_SCROLL_LEFT, facade);
521
        } else if (info.isScrollRight) {
522
            this.fire(EVT_SCROLL_RIGHT, facade);
523
        }
524
 
525
        if (info.isScrollUp) {
526
            this.fire(EVT_SCROLL_UP, facade);
527
        } else if (info.isScrollDown) {
528
            this.fire(EVT_SCROLL_DOWN, facade);
529
        }
530
 
531
        if (info.atBottom && (!lastScroll.atBottom ||
532
                info.scrollHeight > lastScroll.scrollHeight)) {
533
 
534
            this.fire(EVT_SCROLL_TO_BOTTOM, facade);
535
        }
536
 
537
        if (info.atLeft && !lastScroll.atLeft) {
538
            this.fire(EVT_SCROLL_TO_LEFT, facade);
539
        }
540
 
541
        if (info.atRight && (!lastScroll.atRight ||
542
                info.scrollWidth > lastScroll.scrollWidth)) {
543
 
544
            this.fire(EVT_SCROLL_TO_RIGHT, facade);
545
        }
546
 
547
        if (info.atTop && !lastScroll.atTop) {
548
            this.fire(EVT_SCROLL_TO_TOP, facade);
549
        }
550
    },
551
 
552
    // -- Protected Event Handlers ---------------------------------------------
553
 
554
    /**
555
    Handles DOM `scroll` events on the host node.
556
 
557
    @method _afterHostScroll
558
    @param {EventFacade} e
559
    @protected
560
    **/
561
    _afterHostScroll: function (e) {
562
        var self = this;
563
 
564
        clearTimeout(this._scrollTimeout);
565
 
566
        this._scrollTimeout = setTimeout(function () {
567
            self._triggerScroll(e);
568
        }, this._scrollDelay);
569
    },
570
 
571
    /**
572
    Handles browser resize events.
573
 
574
    @method _afterResize
575
    @protected
576
    **/
577
    _afterResize: function () {
578
        this.refreshDimensions();
579
    },
580
 
581
    /**
582
    Caches the `scrollDelay` value after that attribute changes to allow
583
    quicker lookups in critical path code.
584
 
585
    @method _afterScrollDelayChange
586
    @param {EventFacade} e
587
    @protected
588
    **/
589
    _afterScrollDelayChange: function (e) {
590
        this._scrollDelay = e.newVal;
591
    },
592
 
593
    /**
594
    Caches the `scrollMargin` value after that attribute changes to allow
595
    quicker lookups in critical path code.
596
 
597
    @method _afterScrollMarginChange
598
    @param {EventFacade} e
599
    @protected
600
    **/
601
    _afterScrollMarginChange: function (e) {
602
        this._scrollMargin = e.newVal;
603
    },
604
 
605
    /**
606
    Handles DOM `scroll` events on the window.
607
 
608
    @method _afterWindowScroll
609
    @param {EventFacade} e
610
    @protected
611
    **/
612
    _afterWindowScroll: function () {
613
        this._refreshHostBoundingRect();
614
    }
615
}, {
616
    NS: 'scrollInfo',
617
 
618
    ATTRS: {
619
        /**
620
        Number of milliseconds to wait after a native `scroll` event before
621
        firing local scroll events. If another native scroll event occurs during
622
        this time, previous events will be ignored. This ensures that we don't
623
        fire thousands of events when the user is scrolling quickly.
624
 
625
        @attribute scrollDelay
626
        @type Number
627
        @default 50
628
        **/
629
        scrollDelay: {
630
            value: 50
631
        },
632
 
633
        /**
634
        Additional margin in pixels beyond the onscreen region of the host node
635
        that should be considered "onscreen".
636
 
637
        For example, if set to 50, then a `scrollToBottom` event would be fired
638
        when the user scrolls to within 50 pixels of the bottom of the
639
        scrollable region, even if they don't actually scroll completely to the
640
        very bottom pixel.
641
 
642
        This margin also applies to the `getOffscreenNodes()` and
643
        `getOnscreenNodes()` methods by default.
644
 
645
        @attribute scrollMargin
646
        @type Number
647
        @default 50
648
        **/
649
        scrollMargin: {
650
            value: 50
651
        }
652
    }
653
});
654
 
655
 
656
}, '3.18.1', {"requires": ["array-extras", "base-build", "event-resize", "node-pluginhost", "plugin", "selector"]});