Jhernandez has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/183838

Change subject: WIP: Implement infinite scroll independently
......................................................................

WIP: Implement infinite scroll independently

* Change uploads to use extracted component
* [In progress] Change Special:EditWatchlist (WatchList.js) to use it

Change-Id: I3364cf3ea6a61c05df0f5bdf3fdf9748c27683a6
---
M includes/Resources.php
A javascripts/InfiniteScroll.js
M javascripts/specials/uploads/PhotoList.js
3 files changed, 163 insertions(+), 39 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend 
refs/changes/38/183838/1

diff --git a/includes/Resources.php b/includes/Resources.php
index 9ac4986..6ac7aad 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -1326,6 +1326,7 @@
                        'mobile-frontend-photo-upload-user-count',
                ),
                'scripts' => array(
+                       'javascripts/InfiniteScroll.js',
                        'javascripts/specials/uploads/UserGalleryApi.js',
                        'javascripts/specials/uploads/PhotoItem.js',
                        'javascripts/specials/uploads/PhotoList.js',
diff --git a/javascripts/InfiniteScroll.js b/javascripts/InfiniteScroll.js
new file mode 100644
index 0000000..54d6900
--- /dev/null
+++ b/javascripts/InfiniteScroll.js
@@ -0,0 +1,134 @@
+( function ( M, $ ) {
+
+       var EventEmitter = M.require( 'eventemitter' ),
+               InfiniteScroll;
+
+       /**
+        * Class to assist a view in implementing infinite scrolling on some DOM
+        * element.
+        *
+        * @class InfiniteScroll
+        *
+        * Use this class in a view to help it do infinite scrolling.
+        *
+        * 1. Initialize it in the constructor `initialize` and listen to the 
'load'
+        *   event it emits (and call your loading function then)
+        * 2. On preRender (once we have the dom element) set it into the 
infinite
+        *   scrolling object and disable it until we've loaded.
+        * 3. Once you have loaded the list and put it in the dom, enable the
+        *   infinite scrolling detection.
+        *   * Everytime the scroller detection triggers a load, it auto 
disables
+        *     to not trigger multiple times. After you have loaded, manually
+        *     re-enable it.
+        *
+        *              @example
+        *              <code>
+        *              var InfiniteScroll = M.require( 'InfiniteScroll' ),
+        *                      PhotoList;
+        *
+        *              PhotoList = View.extend( {
+        *                      //...
+        *                      initialize: function ( options ) {
+        *                              this.api = new UserGalleryApi( {
+        *                                      username: options.username
+        *                              } );
+        *                              // 1. Set up infinite scroll helper and 
listen to events
+        *                              this.infiniteScroll = new 
InfiniteScroll( 1000 );
+        *                              this.infiniteScroll.on( 'load', 
$.proxy( this, '_loadPhotos' ) );
+        *
+        *                              View.prototype.initialize.apply( this, 
arguments );
+        *                      },
+        *                      preRender: function () {
+        *                              // 2. Disable until we've got the list 
rendered and set DOM el
+        *                              this.infiniteScroll.setElement( 
this.$el );
+        *                              this.infiniteScroll.disable();
+        *                      },
+        *                      _loadPhotos: function () {
+        *                              var self = this;
+        *                              this.api.getPhotos().done( function ( 
photos ) {
+        *                                      // load photos into the DOM ...
+        *                                      // 3. and (re-)enable infinite 
scrolling
+        *                                      self.infiniteScroll.enable();
+        *                              } );
+        *                      }
+        *              } );
+        *              </code>
+        */
+       InfiniteScroll = EventEmitter.extend( {
+               /**
+                * Constructor.
+                * @param {Number} threshold distance in pixels used to 
calculate if scroll
+                * position is near the end of the $el
+                */
+               initialize: function ( threshold ) {
+                       EventEmitter.prototype.initialize.apply( this, 
arguments );
+                       this.threshold = threshold || 1000;
+                       this.enabled = true;
+                       this._bindScroll();
+               },
+               /**
+                * Listen to scroll on window and notify this._onScroll
+                * @method
+                * @private
+                */
+               _bindScroll: function () {
+                       // FIXME: Consider using setInterval instead or some 
sort of
+                       // dethrottling/debouncing to avoid performance 
degradation
+                       // e.g. 
http://benalman.com/projects/jquery-throttle-debounce-plugin/
+                       $( window ).on( 'scroll', $.proxy( this, '_onScroll' ) 
);
+               },
+               /**
+                * Scroll handler. Triggers load event when near the end of the 
container.
+                * @method
+                * @private
+                */
+               _onScroll: function () {
+                       if ( this.$el && this.enabled && this.scrollNearEnd() ) 
{
+                               // Disable when triggering an event. Won't 
trigger again until
+                               // re-enabled.
+                               this.disable();
+                               /**
+                                * @event load
+                                * Fired when scroll bottom has been reached to 
give oportunity to
+                                * load to owners.
+                                */
+                               this.emit( 'load' );
+                       }
+               },
+               /**
+                * Is the scroll position near the end of the container element?
+                * @method
+                * @private
+                * @return {Boolean}
+                */
+               scrollNearEnd: function () {
+                       var scrollBottom = $( window ).scrollTop() + $( window 
).height(),
+                               endPosition = this.$el.offset().top + 
this.$el.outerHeight();
+                       return scrollBottom + this.threshold > endPosition;
+               },
+               /**
+                * Enable the InfiniteScroll so that it triggers events.
+                * @method
+                */
+               enable: function () {
+                       this.enabled = true;
+               },
+               /**
+                * Disable the InfiniteScroll so that it doesn't trigger events.
+                * @method
+                */
+               disable: function () {
+                       this.enabled = false;
+               },
+               /**
+                * Set the element to compare to scroll position to
+                * @param {jQuery.Object} $el jQuery element where we want to 
listen for
+                * infinite scrolling.
+                */
+               setElement: function ( $el ) {
+                       this.$el = $el;
+               }
+       } );
+
+       M.define( 'InfiniteScroll', InfiniteScroll );
+}( mw.mobileFrontend, jQuery ) );
diff --git a/javascripts/specials/uploads/PhotoList.js 
b/javascripts/specials/uploads/PhotoList.js
index e4756d0..883098f 100644
--- a/javascripts/specials/uploads/PhotoList.js
+++ b/javascripts/specials/uploads/PhotoList.js
@@ -3,6 +3,7 @@
                icons = M.require( 'icons' ),
                UserGalleryApi = M.require( 'specials/uploads/UserGalleryApi' ),
                PhotoItem = M.require( 'specials/uploads/PhotoItem' ),
+               InfiniteScroll = M.require( 'InfiniteScroll' ),
                View = M.require( 'View' );
 
        /**
@@ -10,6 +11,7 @@
         * @class PhotoList
         * @uses UserGalleryApi
         * @uses PhotoItem
+        * @uses InfiniteScroll
         * @extends View
         */
        PhotoList = View.extend( {
@@ -23,13 +25,19 @@
                },
                /** @inheritdoc */
                initialize: function ( options ) {
-                       // how close a spinner needs to be to the viewport to 
trigger loading (px)
-                       this.threshold = 1000;
-                       this.shouldLoad = true;
                        this.api = new UserGalleryApi( {
                                username: options.username
                        } );
+                       // Set up infinite scroll
+                       this.infiniteScroll = new InfiniteScroll( 1000 );
+                       this.infiniteScroll.on( 'load', $.proxy( this, 
'_loadPhotos' ) );
                        View.prototype.initialize.apply( this, arguments );
+               },
+               /** @inheritdoc */
+               preRender: function () {
+                       // Disable until we've got the list rendered
+                       this.infiniteScroll.setElement( this.$el );
+                       this.infiniteScroll.disable();
                },
                /** @inheritdoc */
                postRender: function () {
@@ -37,10 +45,6 @@
                        this.$list = this.$( 'ul' );
 
                        this._loadPhotos();
-                       // FIXME: Consider using setInterval instead or some 
sort of dethrottling/debouncing to avoid performance
-                       // degradation
-                       // e.g. 
http://benalman.com/projects/jquery-throttle-debounce-plugin/
-                       $( window ).on( 'scroll', $.proxy( this, '_loadPhotos' 
) );
                },
                /**
                 * Check to see if the current view is an empty list.
@@ -95,16 +99,6 @@
                        M.emit( 'photo-loaded', photoItem.$el );
                },
                /**
-                * Check if the user has scrolled near the end of the list.
-                * @method
-                * @private
-                * @return {Boolean}
-                */
-               _isEndNear: function () {
-                       var scrollBottom = $( window ).scrollTop() + $( window 
).height();
-                       return scrollBottom + this.threshold > 
this.$end.offset().top;
-               },
-               /**
                 * Load photos into the view using {{UserGalleryApi}} when the 
end is near
                 * and no current API requests are underway.
                 * @method
@@ -113,29 +107,24 @@
                _loadPhotos: function () {
                        var self = this;
 
-                       if ( this.shouldLoad && this._isEndNear() ) {
-                               // don't try to load more until current request 
is finished
-                               this.shouldLoad = false;
-
-                               this.api.getPhotos().done( function ( photos ) {
-                                       if ( photos.length ) {
-                                               $.each( photos, function () {
-                                                       self.appendPhoto( this 
);
-                                               } );
-                                               // try loading more when end is 
near only if we got photos last time
-                                               self.shouldLoad = true;
-                                       } else {
-                                               self.$end.remove();
-                                               if ( self.isEmpty() ) {
-                                                       self.emit( 'empty' );
-                                                       self.showEmptyMessage();
-                                               }
+                       this.api.getPhotos().done( function ( photos ) {
+                               if ( photos.length ) {
+                                       $.each( photos, function ( i, photo ) {
+                                               self.appendPhoto( photo );
+                                       } );
+                                       // try loading more when end is near 
only if we got photos last time
+                                       self.infiniteScroll.enable();
+                               } else {
+                                       self.$end.remove();
+                                       if ( self.isEmpty() ) {
+                                               self.emit( 'empty' );
+                                               self.showEmptyMessage();
                                        }
-                               } ).fail( function () {
-                                       // try loading again if request failed
-                                       self.shouldLoad = true;
-                               } );
-                       }
+                               }
+                       } ).fail( function () {
+                               // try loading again if request failed
+                               self.infiniteScroll.enable();
+                       } );
                }
        } );
 

-- 
To view, visit https://gerrit.wikimedia.org/r/183838
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I3364cf3ea6a61c05df0f5bdf3fdf9748c27683a6
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: Jhernandez <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to