~jonas-drange/ubuntu-webcatalog/tos-redirect

« back to all changes in this revision

Viewing changes to src/webcatalog/static/yui/3.10.3/build/node-scroll-info/node-scroll-info.js

  • Committer: Tarmac
  • Author(s): Stephen Stewart
  • Date: 2013-06-26 09:19:32 UTC
  • mfrom: (184.1.4 ubuntu-global-nav)
  • Revision ID: tarmac-20130626091932-8urtuli368k8p7ds
[r=beuno,jonas-drange] add ubuntu global nav to apps.ubuntu.com

Show diffs side-by-side

added added

removed removed

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