src/cm/media/js/lib/yui/yui_3.10.3/build/node-scroll-info/node-scroll-info-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     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"]});