text: 
  // simple app for managing pagination & search of cookbooks.
  var MinimartApp = {
    start: function (opts) {
      new MinimartApp.Router(opts);
      Backbone.history.start();
    }
  };
  
  MinimartApp.Cookbook = Backbone.Model.extend({});
  
  MinimartApp.CookbookList = Backbone.Collection.extend({ model: MinimartApp.Cookbook });
  
  MinimartApp.CookbookListView = Backbone.View.extend({
  
    events: {
      "click #paginator a": 'goToPage',
      "submit #cookbook-search-form": 'search'
    },
  
    initialize: function (opts) {
      this.query     = opts.query;
      this.paginator = opts.paginator;
    },
  
    template: function () {
      if (!this.cachedTemplate) {
        this.cachedTemplate = _.template($('#cookbook-list-template').html());
      }
      return this.cachedTemplate;
    },
  
    render: function () {
      this.$el.html(this.template()({
        query:            this.query,
        paginator:        this.paginator,
        total_cookbooks:  (_.isUndefined(this.paginator) ? this.collection.size() : this.paginator.size()),
        cookbooks:        this.collection.toJSON()
      }));
  
      $('#cookbooks').
        empty().
        append(this.$el);
    },
  
    search: function (ev) {
      ev.preventDefault();
      var query = $(ev.target).find('input[name="query"]').val();
      this.navigate('#search/' + encodeURI(query));
    },
  
    goToPage: function (ev) {
      var self, pageNumber;
      ev.preventDefault();
      self = this;
      pageNumber = parseInt($(ev.target).data('page'));
      $('body').animate({ scrollTop: 0 }, "fast", function () {
        self.navigate('#page/' + pageNumber);
      });
    },
  
    navigate: function (route) {
      this.remove();
      Backbone.history.navigate(route, {trigger: true});
    }
  });
  
  MinimartApp.Router = Backbone.Router.extend({
    routes: {
      "": 'index',
      "search/:query": 'search',
      "page/:pageNumber": 'paginated'
    },
  
    initialize: function (opts) {
      this.collection = opts.collection;
    },
  
    index: function () {
      this.buildView(0).render();
    },
  
    paginated: function (pageNumber) {
      this.buildView(parseInt(pageNumber)).render();
    },
  
    search: function (query) {
      query = decodeURI(query);
  
      var filtered = _.filter(this.collection, function (c) {
        return c.name.match(query);
      });
  
      new MinimartApp.CookbookListView({
        query: query,
        collection: new MinimartApp.CookbookList(filtered)
      }).render();
    },
  
    buildView: function (pageNumber) {
      var paginator, filtered;
  
      paginator = new MinimartApp.Paginator({ collection: this.collection, page: pageNumber });
      filtered  = new MinimartApp.CookbookList(paginator.collectionForPage());
  
      return new MinimartApp.CookbookListView({ collection: filtered, paginator:  paginator });
    }
  });
  
  MinimartApp.Paginator = function (opts) {
    this.pageSize    = opts.pageSize || 10;
    this.currentPage = opts.page || 0;
    this.collection  = opts.collection;
  };
  
  MinimartApp.Paginator.prototype.canShow = function () {
    return this.size() > this.pageSize;
  };
  
  MinimartApp.Paginator.prototype.collectionForPage = function () {
    var lower, upper;
    lower = this.currentPage * this.pageSize;
    upper = (lower + this.pageSize);
    return this.collection.slice(lower, upper);
  };
  
  MinimartApp.Paginator.prototype.canShowNext = function () {
    return this.isPageInRange(this.nextPage());
  };
  
  MinimartApp.Paginator.prototype.nextPage = function () {
    return this.currentPage + 1;
  };
  
  MinimartApp.Paginator.prototype.isPageInRange = function (pageNumber) {
    return (pageNumber * this.pageSize) < this.size();
  };
  
  MinimartApp.Paginator.prototype.canShowPrevious = function () {
    return this.previousPage() >= 0;
  };
  
  MinimartApp.Paginator.prototype.previousPage = function () {
    return this.currentPage - 1;
  };
  
  MinimartApp.Paginator.prototype.size = function () {
    return this.collection.length;
  };
  
  MinimartApp.Paginator.prototype.lastPage = function () {
    return Math.floor(this.collection.length / this.pageSize);
  };
  
  MinimartApp.Paginator.prototype.surroundingPages = function () {
    var startPage, endPage;
    startPage = this.currentPage - 2 > 0 ? this.currentPage - 2 : 0;
    endPage   = this.currentPage + 2 < this.lastPage() ? this.currentPage + 2 : this.lastPage();
    return _.range(startPage, endPage + 1);
  };
  
  MinimartApp.Paginator.prototype.isCurrentPage = function (page) {
    return page === this.currentPage;
  };
  