All files / mobile.startup ScrollEndEventEmitter.js

80% Statements 24/30
60% Branches 9/15
87.5% Functions 7/8
80% Lines 24/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 1721x 1x                                                                                                                             5x 5x 5x 5x   1x             1x   1x                 6x 6x 6x                     4x 4x 4x                     2x     1x 1x                                                     6x 6x                 4x 4x                     3x       1x  
var util = require( './util' ),
	mfExtend = require( './mfExtend' );
 
/**
 * Class to assist a view in implementing infinite scrolling on some DOM
 * element. This module itself is only responsible for emitting an Event when
 * the bottom of an Element is scrolled to.
 *
 * @class ScrollEndEventEmitter
 * @mixins OO.EventEmitter
 *
 * Use this class in a view to help it do infinite scrolling.
 *
 * 1. Initialize it in the constructor `initialize` and listen to the
 *   EVENT_SCROLL_END 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.
 *   - Every time the scroller detection triggers a load, it auto disables
 *     to not trigger multiple times. After you have loaded, manually
 *     re-enable it.
 *
 * Example:
 *     @example
 *     <code>
 *       var
 *         mfExtend = require( './mfExtend' ),
 *         ScrollEndEventEmitter = require( './ScrollEndEventEmitter' ),
 *         eventBus = require( './eventBusSingleton' );
 *       mfExtend( PhotoList, View, {
 *         //...
 *         initialize: function ( options ) {
 *           this.gateway = new PhotoListGateway( {
 *             username: options.username
 *           } );
 *           // 1. Set up infinite scroll helper and listen to events
 *           this.scrollEndEventEmitter = new ScrollEndEventEmitter( eventBus, 1000 );
 *           this.scrollEndEventEmitter.on( ScrollEndEventEmitter.EVENT_SCROLL_END,
 *             this._loadPhotos.bind( this ) );
 *           View.prototype.initialize.apply( this, arguments );
 *         },
 *         preRender: function () {
 *           // 2. Disable until we've got the list rendered and set DOM el
 *           this.scrollEndEventEmitter.setElement( this.$el );
 *           this.scrollEndEventEmitter.disable();
 *         },
 *         _loadPhotos: function () {
 *           var self = this;
 *           this.gateway.getPhotos().then( function ( photos ) {
 *             // load photos into the DOM ...
 *             // 3. and (re-)enable infinite scrolling
 *             self.scrollEndEventEmitter.enable();
 *           } );
 *         }
 *       } );
 *     </code>
 *
 * @fires ScrollEndEventEmitter#ScrollEndEventEmitter-scrollEnd
 * @param {Object} eventBus object to listen for scroll:throttled events
 * @param {number} [threshold=100] distance in pixels used to calculate if scroll
 * position is near the end of the $el
 */
function ScrollEndEventEmitter( eventBus, threshold ) {
	this.threshold = threshold || 100;
	this.eventBus = eventBus;
	this.enable();
	OO.EventEmitter.call( this );
}
OO.mixinClass( ScrollEndEventEmitter, OO.EventEmitter );
 
/**
 * Fired when scroll bottom has been reached.
 *
 * @event ScrollEndEventEmitter#ScrollEndEventEmitter-scrollEnd
 */
ScrollEndEventEmitter.EVENT_SCROLL_END = 'ScrollEndEventEmitter-scrollEnd';
 
mfExtend( ScrollEndEventEmitter, {
	/**
	 * Listen to scroll on window and notify this._onScroll
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 * @private
	 */
	_bindScroll() {
		Eif ( !this._scrollHandler ) {
			this._scrollHandler = this._onScroll.bind( this );
			this.eventBus.on( 'scroll:throttled', this._scrollHandler );
		}
	},
	/**
	 * Unbind scroll handler
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 * @private
	 */
	_unbindScroll() {
		Eif ( this._scrollHandler ) {
			this.eventBus.off( 'scroll:throttled', this._scrollHandler );
			this._scrollHandler = null;
		}
	},
	/**
	 * Scroll handler. Triggers load event when near the end of the container.
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 * @private
	 */
	_onScroll() {
		if ( this.$el && this.enabled && this.scrollNearEnd() ) {
			// Disable when triggering an event. Won't trigger again until
			// re-enabled.
			this.disable();
			this.emit( ScrollEndEventEmitter.EVENT_SCROLL_END );
		}
	},
	/**
	 * Is the scroll position near the end of the container element?
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 * @private
	 * @return {boolean}
	 */
	scrollNearEnd() {
		if ( !this.$el || !this.$el.offset() ) {
			return false;
		}
		var $window = util.getWindow(),
			scrollBottom = $window.scrollTop() + $window.height(),
			endPosition = this.$el.offset().top + this.$el.outerHeight();
		return scrollBottom + this.threshold > endPosition;
	},
	/**
	 * Enable the ScrollEndEventEmitter so that it triggers events.
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 */
	enable() {
		this.enabled = true;
		this._bindScroll();
	},
	/**
	 * Disable the ScrollEndEventEmitter so that it doesn't trigger events.
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 */
	disable() {
		this.enabled = false;
		this._unbindScroll();
	},
	/**
	 * Set the element to compare to scroll position to
	 *
	 * @memberof ScrollEndEventEmitter
	 * @instance
	 * @param {jQuery.Object} $el jQuery element where we want to listen for
	 * scroll end.
	 */
	setElement( $el ) {
		this.$el = $el;
	}
} );
 
module.exports = ScrollEndEventEmitter;