title: Backbone.js fetch() large collection causes script to freeze
body: <p>I have a Jobs table view that displays all the jobs of a user.  The Jobs collection fetch() can potentially return contain thousands of records.  I ran a test and inserted 1000 Job records in the DB and performed a fetch() on the collection.  However, 1000 records seem too much for the browser to handle since inserting 1000 DOM table rows seem to cause the browser to freeze.</p>
  
  <p>Is there a better way to optimize the rendering of the rows so it performs faster?  I know you could always do a partial fetch (fetch initialy 100 records and additionally fetch 100 records  every time the user scrolls to the bottom of the screen), but I'm generally against this idea since scrolling down 100 records and having to wait 3-4 seconds before it renders an additional 100 records seem to result in poor user experience.</p>
  
  <p>Here's my code:</p>
  
  <pre><code>FM.Views.JobTable = Backbone.View.extend({
    initialize: function(){
      _.bindAll(this, 'render', 'refresh', 'appendItem');
      this.collection.bind('add', this.appendItem, this);
      this.collection.bind('reset', this.refresh, this);
    },
  
    render: function(){
      this.el = ich.JobTable({});
      $(this.el).addClass('loading');
      return this;
    },
  
    refresh: function(){
      $('tbody tr', this.el).remove();
      $(this.el).removeClass('loading');
  
      _(this.collection.models).each(function(item){ // in case collection is not empty
        this.appendItem(item);
      }, this);
      return this;    
    },
  
    appendItem: function(item){
      var jobRow = new FM.Views.JobTableRow({
        model: item
      });
      $('tbody', this.el).prepend(jobRow.render().el);
      $(jobRow).bind('FM_JobSelected', this.triggerSelected);
    }
  
  });
  
  FM.Views.JobTableRow = Backbone.View.extend({
    tagName: 'tr',
  
    initialize: function(){
      _.bindAll(this, 'render', 'remove', 'triggerSelected');
      this.model.bind('remove', this.remove);
    },
  
    render: function(){
      var j = this.model.toJSON();
      j.quantity = j.quantity ? number_format(j.quantity, 0) : '';
      j.date_start = date('M j Y', j.date_start);
      j.date_due = j.date_due ? date('M j Y', strtotime(j.date_due)) : '';
      j.paid_class = j.paid;
      j.status_class = j.status;
      j.paid = slug2words(j.paid);
      j.status = slug2words(j.status);
  
      this.el = ich.JobTableRow(j);
      $(this.el).bind('click', this.triggerSelected);
      return this;
    }
  
  });
  </code></pre>