Famo.us Infinite Scroll

Famo.us is a new javascript framework centered on mobile that proposes to solve the performance problem of mobile webapps. After a week of coding with famous I realize that they are very well on the right track to do so. Famo.us changes the way a web developer creates an app and the biggest challenge is to learn this new way of looking at it. You have now a bunch of tools to use and I must say I really like it, but you might also find a bit difficult to do otherwise trivial stuff. While trying to create the functionality of an infinite scrollview for instance, I had to hack it a bit to get the content height for the calculations. (I was told by a Famo.us team dev they are working on a solution for the problem)

So, into it. The goal is to expande a scrollview to emit the necessary events for an infinite scroll implementation. I have created a library for it that you can find at Github. Feel free to check it out and use it.


Extending famo.us core components is as simple as including it and aplying to your new component.

var ScrollView = require('famous/views/Scrollview');

function InfiniteScrollView() {
    ScrollView.apply(this, arguments);

    if (this.options.offset)
        this.setOffset(this.options.offset);

    _monitor.call(this);
}

InfiniteScrollView.prototype = Object.create(ScrollView.prototype);
InfiniteScrollView.prototype.constructor = InfiniteScrollView;

InfiniteScrollView.DEFAULT_OPTIONS = {};

module.exports = InfiniteScrollView;

An then to the new functionality.

function _monitor() {
    this.sync.on('start',function(data) {
        this._setContentSize();
    }.bind(this));

    this.sync.on('update',function(data) {
        if (!this.infiniteScrollDisabled && this._scroller._position + Math.abs(this._pageSpringPosition) + this.getSize()[1] >= this.contentSize - this.offset) {
            this._eventOutput.emit('infiniteScroll');
        }
    }.bind(this));
}

Scrollview emits event on start, update and end, for our goal we would normaly just need update, the sizes of the scrollview container and its content as well as the scroll position. The problem lies on knowing the size of the content. If you look at the dom of a famo.us app, you will notice that the dom's structure is very flat comparing to a normal web app and tree is not very structured (this is intentional as is is one of the way famo.us manages to maintain high rendiring performance).

InfiniteScrollView.prototype._setContentSize = function() {
    var node = this._node;
    this.contentSize = 0;
    for(var i in node._.array) {
        this.contentSize += node._.array[i].getSize()[1];
    }
}

To solve this problem we have to hack a small function where we will read and sum the sizes of all views contained on the scrollview.
I opted to refresh this value every time the user starts scrolling.

if (!this.infiniteScrollDisabled && this._scroller._position + Math.abs(this._pageSpringPosition) + this.getSize()[1] >= this.contentSize - this.offset) {
            this._eventOutput.emit('infiniteScroll');
        }

Now we have the necessary information to create our infinite scroll functionality.

Get the scroll position with this._pageSpringPosition which gives us not the actual position of the scrollview but the page position and add to this._scroller._position which translates into the scroll position within the current page being display at any given time.

Get the scrollview container height with this.getSize()[1].

And the calculated content hight from this.contentSize.


The full code:

define(function(require, exports, module) {
    var ScrollView = require('famous/views/Scrollview');

    function InfiniteScrollView() {
        ScrollView.apply(this, arguments);

        if (this.options.offset)
            this.setOffset(this.options.offset);

        _monitor.call(this);
    }

    InfiniteScrollView.prototype = Object.create(ScrollView.prototype);
    InfiniteScrollView.prototype.constructor = InfiniteScrollView;

    InfiniteScrollView.DEFAULT_OPTIONS = {};

    InfiniteScrollView.prototype.setOffset = function(o) {
        this.offset = o;
    }

    InfiniteScrollView.prototype._setContentSize = function() {
        var node = this._node;
        this.contentSize = 0;
        for(var i in node._.array) {
            this.contentSize += node._.array[i].getSize()[1];
        }
    }

    function _monitor() {
        this.sync.on('start',function(data) {
            this._setContentSize();
        }.bind(this));

        this.sync.on('update',function(data) {
            if (!this.infiniteScrollDisabled && -this._pageSpringPosition + this.getSize()[1] >= this.contentSize - this.offset) {
                this._eventOutput.emit('infiniteScroll');
            }
        }.bind(this));
    }

    module.exports = InfiniteScrollView;
});

Now you can use it on you project like:

var InfiniteScrollView  = require('famous-infinitescroll');

this.contactsView = [];

this.scrollview = new InfiniteScrollView({
    margin: 1000,
    offset: 1000
});
this.viewSequence = new ViewSequence(this.contactsView);
this.scrollview.sequenceFrom(this.viewSequence);

this.scrollview.on('infiniteScroll', function(data) {
    this.scrollview.infiniteScrollDisabled = true;
    console.log('infiniteScroll');
    setTimeout(function () {
        this.scrollview.infiniteScrollDisabled = false;
    }.bind(this), 1000);
}.bind(this));

Set this.scrollview.infiniteScrollDisabled = true; whenever you need to stop it from firing infiniteScroll events. and this.scrollview.infiniteScrollDisabled = false; to resume it.

You can also set the offset with this.setOffset(this.scrollview.getSize()[1]);, in this case I am setting it to the scrollview container heigh.


Example:

See the Pen mCots by João Ribeiro (@JonnyBGod) on CodePen.


References:

http://famo.us
https://github.com/JonnyBGod/famous-infinitescroll