lightweight infinite scroll with backbone.js

10,371

Solution 1

It's normal that myApp.routers.list.index() doesn't work if there is the declaration of the Router, you need to call the instance of the router. There are many things to say and I think the best explication is to see the code work and if It's that you want, learn the code.

I created an infinite listing with a "More" button to add models on the listing with using your code. The demo is on nodejitsu here : http://infinite-scroll.eu01.aws.af.cm/

You can show the complete code (client and server) on my gist on GitHub : https://gist.github.com/1522344 (I added a comment to explain how use the files)

Solution 2

Here's a lightweight implementation https://github.com/joneath/infiniScroll.js

Solution 3

Here is another solution http://backbonetutorials.com/infinite-scrolling/

Solution 4

I have created an extend of Backbone.Collection for easy using:

_.extend Backbone.Collection.prototype,
  options:
    infinitescroll:
      success: $.noop
      error: $.noop
      bufferPx: 40
      scrollPx: 150
      page:
        current: 0
        per: null
      state:
          isDuringAjax: false
          isDone: false
          isInvalid: false
      loading:
        wrapper: 'backbone-infinitescroll-wrapper'
        loadingId: 'backbone-infinitescroll-loading'
        loadingImg: 'loading.gif'
        loadingMsg: '<em>Loading ...</em>'
        finishedMsg: '<em>No more</em>'
        msg: null
        speed: 'fast'

  infinitescroll: (options={})->
    # NOTE: coffeescript cannot deal with nested scope!
    that = @

    _.extend(@options.infinitescroll, options.infinitescroll) if options.infinitescroll

    infinitescroll_options = @options.infinitescroll

    # where we want to place the load message in?
    infinitescroll_options.loading.wrapper = $(infinitescroll_options.loading.wrapper)
    if !infinitescroll_options.loading.msg and infinitescroll_options.loading.wrapper.size() > 0
      infinitescroll_options.loading.msg = $('<div/>', {
        id: infinitescroll_options.loading.loadingId
      }).html('<img alt="'+$(infinitescroll_options.loading.loadingMsg).text()+'" src="' + infinitescroll_options.loading.loadingImg + '" /><div>' + infinitescroll_options.loading.loadingMsg + '</div>')
      infinitescroll_options.loading.msg.appendTo(infinitescroll_options.loading.wrapper).hide()
    else
      infinitescroll_options.loading.msg = null

    fetch_options = _.omit(options, 'infinitescroll')
    infinitescroll_fetch = ()=>
      # mark the XHR request
      infinitescroll_options.state.isDuringAjax = true

      # increase page count
      infinitescroll_options.page.current++

      payload = {
        page: infinitescroll_options.page.current
      }
      payload['limit'] = infinitescroll_options.page.per if infinitescroll_options.page.per isnt null

      _.extend(fetch_options, {
        remove: false
        data: payload
      })

      if infinitescroll_options.loading.msg
        # preload loading.loadingImg
        (new Image()).src = infinitescroll_options.loading.loadingImg if infinitescroll_options.loading.loadingImg

        infinitescroll_options.loading.msg.fadeIn infinitescroll_options.loading.speed, ()->
          that.fetch(fetch_options)
          .success (data, state, jqXHR)=>
            infinitescroll_options.state.isDuringAjax = false
            infinitescroll_options.state.isDone = true if _.size(data) is 0

            infinitescroll_options.loading.msg.fadeOut infinitescroll_options.loading.speed, ()->
              infinitescroll_options.success.call(data, state, jqXHR) if _.isFunction(infinitescroll_options.success)
              return
            return
          .error (data, state, jqXHR)=>
            infinitescroll_options.state.isDuringAjax = false
            infinitescroll_options.state.isInvalid = true

            infinitescroll_options.loading.msg.fadeOut infinitescroll_options.loading.speed, ()->
              infinitescroll_options.error.call(data, state, jqXHR) if _.isFunction(infinitescroll_options.error)
              return
            return
          return

      else
        that.fetch(fetch_options)
        .success (data, state, jqXHR)=>
          infinitescroll_options.state.isDuringAjax = false
          infinitescroll_options.state.isDone = true if _.size(data) is 0

          infinitescroll_options.success.call(data, state, jqXHR) if _.isFunction(infinitescroll_options.success)
          return

        .error (data, state, jqXHR)=>
          infinitescroll_options.state.isDuringAjax = false
          infinitescroll_options.state.isInvalid = true

          infinitescroll_options.error.call(data, state, jqXHR) if _.isFunction(infinitescroll_options.error)
          return
      return

    $(document).scroll ()->
      $doc = $(document)
      isNearBottom = ()->
        bottomPx = 0 + $doc.height() - $doc.scrollTop() - $(window).height()

        # if distance remaining in the scroll (including buffer) is less than expected?
        (bottomPx - infinitescroll_options.bufferPx) < infinitescroll_options.scrollPx

      return if infinitescroll_options.state.isDuringAjax || infinitescroll_options.state.isDone || infinitescroll_options.state.isInvalid || !isNearBottom()

      infinitescroll_fetch()
      return


    infinitescroll_fetch()
    return

You can see the implementation at https://gist.github.com/mcspring/7655861

Share:
10,371
pedalpete
Author by

pedalpete

Originally from Whistler, Canada, now living in Bondi Beach, Aus. I like building interesting things, algorithms, UX/UI, getting into hardware and RaspberryPi.

Updated on June 14, 2022

Comments

  • pedalpete
    pedalpete almost 2 years

    i've looked at pagination in backbone https://gist.github.com/838460, and it all seems very heavy handed for what I'm looking for.

    I want to do an infinite scroll type paging, and I'm new to backbone, so maybe I'm just not understaning it correctly.

    what I thought I would do is get the first collection, click a 'next' button, and get the results and just append that to the original collection and render the newly added items.

    So I have this in my Router I have an index function

        if(!myApp.list){
            myApp.list = new myApp.collections.list;
            myApp.list.page = 1;
            } else {
            myApp.list.page++;
            }
            myApp.list.url='/recipes?page='+myApp.list.page;
    
            myApp.list.fetch({
                add: true,
                success: function() {
                    new myApp.views.list({ collection: myApp.list});
                },
                error: function() {
                    new Error({ message: "Error loading documents." });
                }
            });
    

    which will create the collection if it does't exist, and if it does exist, increment the 'page' before requesting the next items in the list.

    so the first part of my question is, is there anything wrong with this way of doing things?? Seems much simpler than the other solutions I've seen.

    Question #2 seems ridiculous, but how do I then trigger the 'next' button to get the next list??

    In my view, I have a 'next' button, but calling myApp.routers.list.index or myApp.views.list doesn't give me an updated list.

  • pedalpete
    pedalpete over 12 years
    thanks for going through all that work @Atinux, I think I have a much better understanding now. And from your response, I assume there is nothing wrong with doing it this way? It does seem much clearer than the other methods I've seen.
  • Atinux
    Atinux over 12 years
    I don't think there is something wrong this way. If there is a best practice to do this, please let me know. In my mind, the simplest way is the better, for the code and for the user.
  • Dave Stibrany
    Dave Stibrany almost 12 years
    This plugin is great! Simple and well written.
  • Zach
    Zach over 11 years
    Just so you know, the nodejitsu link is broken.
  • Brad Larson
    Brad Larson over 10 years
    At some point, that gist is going to die. We prefer if you put the code directly into your answer, like I did here. That prevents this from going away when your gist does.
  • Upperstage
    Upperstage almost 10 years
    Link to example is broken.