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