You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by dc...@apache.org on 2014/03/07 01:18:32 UTC

[49/50] [abbrv] couchdb commit: updated refs/heads/2041-update-ibrowse to 948e7d9

Fauxton: Improved pagination

This is an improvement and fix on the current pagination. This fixes
pagination so that it works with all query options.

Pagination in Couchdb is quite complex
and there are plenty of situations to cater for. This new pagination
works quite differently to the previous way we had it working for a
user.

A user can set the number of documents they want to view on a page. This
is not related to limit options in the query options - the limit option
is an overall cap of how many documents to paginate too. A limit of none
is possible and is the default. If the limit option is set to 50 and a
user wants 10 docs per page, the would then be able to paginate 3 pages
before hitting the end.

Another change is that the api url and browser url does not change when
we paginate. That happens internally and hence the new addition of
urlParams and docParams. This allows Fauxton to keep track of the
parameters that the user sees and the parameters that the document needs
to paginate.

Fixes COUCHDB-2067


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/4e60f0b7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/4e60f0b7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/4e60f0b7

Branch: refs/heads/2041-update-ibrowse
Commit: 4e60f0b7a7b26662dde0ebc0a73c70df54e0f309
Parents: 0782a44
Author: Garren Smith <ga...@gmail.com>
Authored: Tue Jan 28 16:30:38 2014 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Thu Mar 6 11:48:09 2014 +0200

----------------------------------------------------------------------
 src/fauxton/app/addons/databases/resources.js   |   2 +-
 .../app/addons/databases/templates/item.html    |   2 +-
 src/fauxton/app/addons/databases/views.js       |   3 +-
 .../addons/documents/assets/less/documents.less |   8 +
 src/fauxton/app/addons/documents/resources.js   | 211 +++--
 src/fauxton/app/addons/documents/routes.js      | 192 +++--
 .../documents/templates/advanced_options.html   |   8 +-
 .../documents/templates/all_docs_list.html      |  15 +-
 .../documents/templates/all_docs_number.html    |  24 +-
 .../app/addons/documents/templates/sidebar.html |   4 +-
 .../addons/documents/templates/view_editor.html |   4 +-
 .../app/addons/documents/tests/resourcesSpec.js |  27 -
 src/fauxton/app/addons/documents/views.js       | 241 +++---
 src/fauxton/app/addons/fauxton/base.js          |  14 +-
 src/fauxton/app/addons/fauxton/components.js    | 127 ++-
 .../app/addons/fauxton/tests/paginateSpec.js    |  18 -
 src/fauxton/test/mocha/chai.js                  | 804 ++++++++++++++-----
 src/fauxton/test/test.config.underscore         |   4 +
 18 files changed, 1153 insertions(+), 555 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/databases/resources.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/databases/resources.js b/src/fauxton/app/addons/databases/resources.js
index ea1aed2..80cd533 100644
--- a/src/fauxton/app/addons/databases/resources.js
+++ b/src/fauxton/app/addons/databases/resources.js
@@ -22,7 +22,7 @@ define([
 function(app, FauxtonAPI, Documents) {
   var Databases = FauxtonAPI.addon();
 
-  Databases.DocLimit = 20;
+  Databases.DocLimit = 100;
 
   Databases.Model = FauxtonAPI.Model.extend({
     initialize: function(options) {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/databases/templates/item.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/databases/templates/item.html b/src/fauxton/app/addons/databases/templates/item.html
index e2f8071..549f421 100644
--- a/src/fauxton/app/addons/databases/templates/item.html
+++ b/src/fauxton/app/addons/databases/templates/item.html
@@ -13,7 +13,7 @@ the License.
 -->
 
 <td>
-  <a href="#/database/<%=encoded%>/_all_docs?limit=<%=docLimit%>"><%= database.get("name") %></a>
+  <a href="#/database/<%=encoded%>/_all_docs"><%= database.get("name") %></a>
 </td>
 <td><%= database.status.humanSize() %></td>
 <td><%= database.status.numDocs() %></td>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/databases/views.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/databases/views.js b/src/fauxton/app/addons/databases/views.js
index 7f23d65..a56267f 100644
--- a/src/fauxton/app/addons/databases/views.js
+++ b/src/fauxton/app/addons/databases/views.js
@@ -31,8 +31,7 @@ function(app, Components, FauxtonAPI, Databases) {
       
       return {
         encoded: app.utils.safeURLName(this.model.get("name")),
-        database: this.model,
-        docLimit: Databases.DocLimit
+        database: this.model
       };
     }
   });

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/assets/less/documents.less
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/assets/less/documents.less b/src/fauxton/app/addons/documents/assets/less/documents.less
index 9dee85e..c30a9af 100644
--- a/src/fauxton/app/addons/documents/assets/less/documents.less
+++ b/src/fauxton/app/addons/documents/assets/less/documents.less
@@ -23,6 +23,14 @@ button.beautify {
 	margin-top: 20px;
 }
 
+#per-page {
+  float: right;
+
+  #select-per-page {
+    margin-top: 10px;
+  }
+  
+}
 
 /** used in all_docs_list.html **/
 .view {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/resources.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/resources.js b/src/fauxton/app/addons/documents/resources.js
index adfee1f..c0b736f 100644
--- a/src/fauxton/app/addons/documents/resources.js
+++ b/src/fauxton/app/addons/documents/resources.js
@@ -18,6 +18,92 @@ define([
 function(app, FauxtonAPI) {
   var Documents = FauxtonAPI.addon();
 
+  Documents.QueryParams = (function () {
+    var _eachParams = function (params, action) {
+      _.each(['startkey', 'endkey', 'key'], function (key) {
+        if (_.has(params, key)) {
+          params[key] = action(params[key]);
+        }
+      });
+
+      return params;
+    };
+
+    return {
+      parse: function (params) {
+        return _eachParams(params, JSON.parse);
+      },
+
+      stringify: function (params) {
+        return _eachParams(params, JSON.stringify);
+      }
+    };
+  })();
+
+  Documents.paginate = {
+    history: [],
+    calculate: function (doc, defaultParams, currentParams, _isAllDocs) {
+      var docId = '',
+          lastId = '',
+          isView = !!!_isAllDocs,
+          key;
+
+      if (currentParams.keys) {
+        throw "Cannot paginate when keys is specfied";
+      }
+
+      if (_.isUndefined(doc)) {
+        throw "Require docs to paginate";
+      }
+
+      // defaultParams should always override the user-specified parameters
+      _.extend(currentParams, defaultParams);
+
+      lastId = doc.id || doc._id;
+
+      // If we are paginating on a view, we need to set a ``key`` and a ``docId``
+      // and expect that they are different values.
+      if (isView) {
+        key = doc.key;
+        docId = lastId;
+      } else {
+        docId = key = lastId;
+      }
+
+      // Set parameters to paginate
+      if (isView) {
+        currentParams.startkey_docid = docId;
+        currentParams.startkey = key;
+      } else if (currentParams.startkey) {
+        currentParams.startkey = key;
+      } else {
+        currentParams.startkey_docid = docId;
+      }
+
+      return currentParams;
+    },
+
+    next: function (docs, currentParams, perPage, _isAllDocs) {
+      var params = {limit: perPage, skip: 1},
+          doc = _.last(docs);
+          
+      this.history.push(_.clone(currentParams));
+      return this.calculate(doc, params, currentParams, _isAllDocs);
+    },
+
+    previous: function (docs, currentParams, perPage, _isAllDocs) {
+      var params = this.history.pop(),
+          doc = _.first(docs);
+
+      params.limit = perPage;
+      return params;
+    },
+
+    reset: function () {
+      this.history = [];
+    } 
+  };
+
   Documents.Doc = FauxtonAPI.Model.extend({
     idAttribute: "_id",
     documentation: function(){
@@ -274,20 +360,31 @@ function(app, FauxtonAPI) {
 
   Documents.AllDocs = FauxtonAPI.Collection.extend({
     model: Documents.Doc,
+    isAllDocs: true,
     documentation: function(){
       return "docs";
     },
     initialize: function(_models, options) {
       this.database = options.database;
-      this.params = options.params;
-      this.skipFirstItem = false;
-
+      this.params = _.clone(options.params);
       this.on("remove",this.decrementTotalRows , this);
+      this.perPageLimit = options.perPageLimit || 20;
+
+      if (!this.params.limit) {
+        this.params.limit = this.perPageLimit; 
+      }
     },
 
-    url: function(context) {
+    url: function(context, params) {
       var query = "";
-      if (this.params) {
+
+      if (params) {
+        if (!_.isEmpty(params)) {
+          query = "?" + $.param(params);
+        } else {
+          query = '';
+        }
+      } else if (this.params) {
         query = "?" + $.param(this.params);
       }
 
@@ -314,34 +411,13 @@ function(app, FauxtonAPI) {
       });
     },
 
-    urlNextPage: function (num, lastId) {
-      if (!lastId) {
-        var doc = this.last();
-
-        if (doc) {
-          lastId = doc.id;
-        } else {
-          lastId = '';
-        }
-      }
-
-      this.params.startkey_docid = '"' + lastId + '"';
-      this.params.startkey = '"' + lastId + '"';
-      // when paginating forward, fetch 21 and don't show
-      // the first item as it was the last item in the previous list
-      this.params.limit = num + 1;
-      return this.url('app');
+    updateLimit: function (limit) {
+      this.perPageLimit = limit;
+      this.params.limit = limit;
     },
 
-    urlPreviousPage: function (num, params) {
-      if (params) { 
-        this.params = params;
-      } else {
-        this.params = {reduce: false};
-      }
-
-      this.params.limit = num;
-      return this.url('app'); 
+    updateParams: function (params) {
+      this.params = params;
     },
 
     totalRows: function() {
@@ -359,18 +435,6 @@ function(app, FauxtonAPI) {
       return this.viewMeta.update_seq || false;
     },
 
-    recordStart: function () {
-      if (this.viewMeta.offset === 0) {
-        return 1;
-      }
-
-      if (this.skipFirstItem) {
-        return this.viewMeta.offset + 2;
-      }
-
-      return this.viewMeta.offset + 1;
-    },
-
     parse: function(resp) {
       var rows = resp.rows;
 
@@ -409,11 +473,23 @@ function(app, FauxtonAPI) {
       this.view = options.view;
       this.design = options.design.replace('_design/','');
       this.skipFirstItem = false;
+      this.perPageLimit = options.perPageLimit || 20;
+
+      if (!this.params.limit) {
+        this.params.limit = this.perPageLimit; 
+      }
+
     },
 
-    url: function(context) {
+    url: function(context, params) {
       var query = "";
-      if (this.params) {
+      if (params) {
+        if (!_.isEmpty(params)) {
+          query = "?" + $.param(params);
+        } else {
+          query = '';
+        }
+      } else if (this.params) {
         query = "?" + $.param(this.params);
       }
       
@@ -430,42 +506,18 @@ function(app, FauxtonAPI) {
       return url.join("/") + query;
     },
 
-    urlNextPage: function (num, lastId) {
-      if (!lastId) {
-        lastDoc = this.last();
-      }
-
-      var id = lastDoc.get("id");
-      if (id) {
-        this.params.startkey_docid = id;
-      }
-
-      this.params.startkey =  JSON.stringify(lastDoc.get('key'));
-      this.params.limit = num + 1;
-      return this.url('app');
+    updateParams: function (params) {
+      this.params = params;
     },
 
-     urlPreviousPage: function (num, params) {
-      if (params) { 
-        this.params = params;
-      } else {
-        this.params = {reduce: false};
+    updateLimit: function (limit) {
+      if (this.params.startkey_docid && this.params.startkey) {
+        //we are paginating so set limit + 1
+        this.params.limit = limit + 1;
+        return;
       }
 
-      this.params.limit = num;
-      return this.url('app');
-    },
-
-    recordStart: function () {
-      if (this.viewMeta.offset === 0) {
-        return 1;
-      }
-
-      if (this.skipFirstItem) {
-        return this.viewMeta.offset + 2;
-      }
-
-      return this.viewMeta.offset + 1;
+      this.params.limit = limit;
     },
 
     totalRows: function() {
@@ -565,6 +617,7 @@ function(app, FauxtonAPI) {
 
       return timeString;
     }
+
   });
 
   
@@ -619,10 +672,6 @@ function(app, FauxtonAPI) {
       return deferred;
     },
 
-    recordStart: function () {
-      return 1;
-    },
-
     totalRows: function() {
       return this.viewMeta.total_rows || "unknown";
     },

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/routes.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/routes.js b/src/fauxton/app/addons/documents/routes.js
index 1510485..f340195 100644
--- a/src/fauxton/app/addons/documents/routes.js
+++ b/src/fauxton/app/addons/documents/routes.js
@@ -155,13 +155,11 @@ function(app, FauxtonAPI, Documents, Databases) {
       "route:updateAllDocs": "updateAllDocsFromView",
       "route:updatePreviewDocs": "updateAllDocsFromPreview",
       "route:reloadDesignDocs": "reloadDesignDocs",
-      "route:paginate": "paginate"
+      "route:paginate": "paginate",
+      "route:perPageChange": "perPageChange"
     },
 
     initialize: function (route, masterLayout, options) {
-      var docOptions = app.getParams();
-      docOptions.include_docs = true;
-
       this.databaseName = options[0];
 
       this.data = {
@@ -170,9 +168,11 @@ function(app, FauxtonAPI, Documents, Databases) {
 
       this.data.designDocs = new Documents.AllDocs(null, {
         database: this.data.database,
-        params: {startkey: '"_design"',
+        params: {
+          startkey: '"_design"',
           endkey: '"_design1"',
-          include_docs: true}
+          include_docs: true
+        }
       });
 
       this.sidebar = this.setView("#sidebar-content", new Documents.Views.Sidebar({
@@ -185,12 +185,32 @@ function(app, FauxtonAPI, Documents, Databases) {
       return this.data.designDocs.fetch();
     },
 
+    createParams: function (options) {
+      var urlParams = app.getParams(options);
+      return {
+        urlParams: urlParams,
+        docParams: _.extend(_.clone(urlParams), {limit: this.getDocPerPageLimit(urlParams, 20)})
+      };
+    },
+
+    /*
+    * docParams are the options collection uses to fetch from the server 
+    * urlParams are what are shown in the url and to the user
+    * They are not the same when paginating
+    */
     allDocs: function(databaseName, options) {
-      var docOptions = app.getParams(options);
+      var params = this.createParams(options),
+          urlParams = params.urlParams,
+          docParams = params.docParams;
+
+      if (this.eventAllDocs) {
+        this.eventAllDocs = false;
+        return;
+      }
 
-      this.data.database.buildAllDocs(docOptions);
+      this.data.database.buildAllDocs(docParams);
 
-      if (docOptions.startkey && docOptions.startkey.indexOf('_design') > -1) {
+      if (docParams.startkey && docParams.startkey.indexOf('_design') > -1) {
         this.sidebar.setSelectedTab('design-docs');
       } else {
         this.sidebar.setSelectedTab('all-docs');
@@ -206,22 +226,29 @@ function(app, FauxtonAPI, Documents, Databases) {
       this.setView("#dashboard-upper-content", new Documents.Views.AllDocsLayout({
         database: this.data.database,
         collection: this.data.database.allDocs,
-        params: docOptions
+        params: urlParams,
+        docParams: docParams
       }));
 
       this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
-        collection: this.data.database.allDocs
+        collection: this.data.database.allDocs,
+        docParams: docParams,
+        params: urlParams
       }));
 
       this.crumbs = [
         {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)}
       ];
 
-      this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
+      this.apiUrl = [this.data.database.allDocs.url("apiurl", urlParams), this.data.database.allDocs.documentation() ];
+      //reset the pagination history - the history is used for pagination.previous
+      Documents.paginate.reset();
     },
 
     viewFn: function (databaseName, ddoc, view) {
-      var params = app.getParams(),
+      var params = this.createParams(),
+          urlParams = params.urlParams,
+          docParams = params.docParams;
           decodeDdoc = decodeURIComponent(ddoc);
 
       view = view.replace(/\?.*$/,'');
@@ -230,7 +257,7 @@ function(app, FauxtonAPI, Documents, Databases) {
         database: this.data.database,
         design: decodeDdoc,
         view: view,
-        params: params
+        params: docParams
       });
 
       var ddocInfo = {
@@ -243,7 +270,7 @@ function(app, FauxtonAPI, Documents, Databases) {
         model: this.data.database,
         ddocs: this.data.designDocs,
         viewName: view,
-        params: params,
+        params: urlParams,
         newView: false,
         database: this.data.database,
         ddocInfo: ddocInfo
@@ -256,7 +283,9 @@ function(app, FauxtonAPI, Documents, Databases) {
         collection: this.data.indexedDocs,
         nestedView: Documents.Views.Row,
         viewList: true,
-        ddocInfo: ddocInfo
+        ddocInfo: ddocInfo,
+        docParams: docParams,
+        params: urlParams
       }));
 
       this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + '_' + app.utils.removeSpecialCharacters(view));
@@ -267,15 +296,15 @@ function(app, FauxtonAPI, Documents, Databases) {
         ];
       };
 
-      this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
+      this.apiUrl = [this.data.indexedDocs.url("apiurl", urlParams), "docs"];
+      Documents.paginate.reset();
     },
 
     newViewEditor: function () {
       var params = app.getParams();
 
-      if (this.toolsView) {
-        this.toolsView.remove();
-      }
+      this.toolsView && this.toolsView.remove();
+      this.documentsView && this.documentsView.remove();
 
       this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
         ddocs: this.data.designDocs,
@@ -290,39 +319,41 @@ function(app, FauxtonAPI, Documents, Databases) {
           {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)},
         ];
       };
+
+      Documents.paginate.reset();
     },
 
     updateAllDocsFromView: function (event) {
       var view = event.view,
-          docOptions = app.getParams(),
-          ddoc = event.ddoc;
+          params = this.createParams(),
+          urlParams = params.urlParams,
+          docParams = params.docParams,
+          ddoc = event.ddoc,
+          collection;
 
-      this.documentsView && this.documentsView.remove();
+      docParams.limit = this.getDocPerPageLimit(urlParams, this.documentsView.perPage());
+      this.documentsView.forceRender();
 
       if (event.allDocs) {
-        this.data.database.buildAllDocs(docOptions);
-        this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
-          collection: this.data.database.allDocs
-        }));
-        //this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
-        return;
-      }
+        this.eventAllDocs = true; // this is horrible. But I cannot get the trigger not to fire the route!
+        this.data.database.buildAllDocs(docParams);
+        collection = this.data.database.allDocs;
 
-      this.data.indexedDocs = new Documents.IndexCollection(null, {
-        database: this.data.database,
-        design: ddoc,
-        view: view,
-        params: app.getParams()
-      });
+      } else {
+        collection = this.data.indexedDocs = new Documents.IndexCollection(null, {
+          database: this.data.database,
+          design: ddoc,
+          view: view,
+          params: docParams
+        });
 
-      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
-        database: this.data.database,
-        collection: this.data.indexedDocs,
-        nestedView: Documents.Views.Row,
-        viewList: true
-      }));
+      }
+
+      this.documentsView.setCollection(collection);
+      this.documentsView.setParams(docParams, urlParams);
 
-      this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
+      this.apiUrl = [collection.url("apiurl", urlParams), "docs"];
+      Documents.paginate.reset();
     },
 
     updateAllDocsFromPreview: function (event) {
@@ -345,14 +376,49 @@ function(app, FauxtonAPI, Documents, Databases) {
       }));
     },
 
-    paginate: function (direction) {
-      _.extend(this.documentsView.collection.params, app.getParams());
+    perPageChange: function (perPage) {
+      var params = app.getParams();
+      this.perPage = perPage;
+      this.documentsView.updatePerPage(perPage);
+      this.documentsView.forceRender();
+      params.limit = perPage;
+      this.documentsView.collection.params = params;
+      this.setDocPerPageLimit(perPage);
+    },
+
+    paginate: function (options) {
+      var params = {},
+          urlParams = app.getParams(),
+          collection = this.documentsView.collection;
+
       this.documentsView.forceRender();
-      if (direction === 'next') {
-        this.documentsView.collection.skipFirstItem = true;
+
+      // this is really ugly. But we basically need to make sure that
+      // all parameters are in the correct state and have been parsed before we
+      // calculate how to paginate the collection
+      collection.params = Documents.QueryParams.parse(collection.params);
+      urlParams = Documents.QueryParams.parse(urlParams);
+
+      if (options.direction === 'next') {
+          params = Documents.paginate.next(collection.toJSON(), 
+                                           collection.params,
+                                           options.perPage, 
+                                           !!collection.isAllDocs);
       } else {
-        this.documentsView.collection.skipFirstItem = false;
+          params = Documents.paginate.previous(collection.toJSON(), 
+                                               collection.params, 
+                                               options.perPage, 
+                                               !!collection.isAllDocs);
       }
+
+      // use the perPage sent from IndexPagination as it calculates how many
+      // docs to fetch for next page
+      params.limit = options.perPage;
+
+      // again not pretty but need to make sure all the parameters can be correctly
+      // built into a query
+      params = Documents.QueryParams.stringify(params);
+      collection.updateParams(params);
     },
 
     reloadDesignDocs: function (event) {
@@ -361,6 +427,32 @@ function(app, FauxtonAPI, Documents, Databases) {
       if (event && event.selectedTab) {
         this.sidebar.setSelectedTab(event.selectedTab);
       }
+    },
+
+    setDocPerPageLimit: function (perPage) {
+      window.localStorage.setItem('fauxton:perpage', perPage);
+    },
+
+
+    getDocPerPageLimit: function (urlParams, perPage) {
+      var storedPerPage = perPage;
+
+      if (window.localStorage) {
+        storedPerPage = window.localStorage.getItem('fauxton:perpage');
+
+        if (!storedPerPage) {
+          this.setDocPerPageLimit(perPage);
+          storedPerPage = perPage;
+        } else {
+          storedPerPage = parseInt(storedPerPage, 10);
+        }
+      } 
+
+      if (!urlParams.limit || urlParams.limit > storedPerPage) {
+        return storedPerPage;
+      } else {
+        return urlParams.limit;
+      }
     }
 
   });
@@ -383,9 +475,9 @@ function(app, FauxtonAPI, Documents, Databases) {
       this.databaseName = options[0];
       this.database = new Databases.Model({id: this.databaseName});
 
-      var docOptions = app.getParams();
+      var docParams = app.getParams();
 
-      this.database.buildChanges(docOptions);
+      this.database.buildChanges(docParams);
 
       this.setView("#tabs", new Documents.Views.Tabs({
         collection: this.designDocs,

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/templates/advanced_options.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/advanced_options.html b/src/fauxton/app/addons/documents/templates/advanced_options.html
index bea256a..e282c62 100644
--- a/src/fauxton/app/addons/documents/templates/advanced_options.html
+++ b/src/fauxton/app/addons/documents/templates/advanced_options.html
@@ -12,7 +12,7 @@ License for the specific language governing permissions and limitations under
 the License.
 -->
 <div class="errors-container"></div>
-<form class="view-query-update custom-inputs">
+<form class="js-view-query-update custom-inputs">
   <div class="controls-group">
     <div class="row-fluid">
       <div class="controls controls-row">
@@ -68,10 +68,12 @@ the License.
           <select name="limit" class="input-small">
             <option>5</option>
             <option>10</option>
-            <option selected="selected">20</option>
+            <option >20</option>
             <option>30</option>
             <option>50</option>
-            <option>100</option>
+            <option >100</option>
+            <option>500</option>
+            <option selected="selected">None</option>
           </select>
         </label>
         <div class="checkbox inline">  

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/templates/all_docs_list.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/all_docs_list.html b/src/fauxton/app/addons/documents/templates/all_docs_list.html
index cdec81e..a521ff9 100644
--- a/src/fauxton/app/addons/documents/templates/all_docs_list.html
+++ b/src/fauxton/app/addons/documents/templates/all_docs_list.html
@@ -29,15 +29,16 @@ the License.
   <p>
 
   <div id="item-numbers"> </div>
-
-  <% if (requestDuration) { %>
-    <span class="view-request-duration pull-right">
-    View request duration: <strong> <%= requestDuration %> </strong> 
-    </span>
-  <% } %>
-  </p>
   <table class="all-docs table table-striped table-condensed">
     <tbody></tbody>
   </table>
+  
+  <% if (endOfResults) { %>  
+  <div class="text-center well">
+    <p class="muted">
+      End of results - <a id="js-end-results" href="#query" data-bypass="true" data-toggle="tab">edit query</a>
+    </p>
+    </div>
+  <% } %>
   <div id="documents-pagination"></div>
 </div>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/templates/all_docs_number.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/all_docs_number.html b/src/fauxton/app/addons/documents/templates/all_docs_number.html
index df8fe07..0461a4b 100644
--- a/src/fauxton/app/addons/documents/templates/all_docs_number.html
+++ b/src/fauxton/app/addons/documents/templates/all_docs_number.html
@@ -11,13 +11,25 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 License for the specific language governing permissions and limitations under
 the License.
 -->
-<% if (totalRows === "unknown"){ %>
-  Showing 0 documents. <a href="#/database/<%=database%>/new"> Create your first document.</a>
-<% } else if (showNumbers) { %>
-  Showing <%=offset%> - <%= numModels %> of <%= totalRows %> rows
+<% if (totalRows === "unknown" || totalRows === 0){ %>
+Showing 0 documents. <a href="#/database/<%=database%>/new"> Create your first document.</a>
 <% } else { %>
-  Showing <%=pageStart%> - <%= pageEnd %>
+Showing <%=pageStart%> - <%= pageEnd %>
 <%}%>
 <% if (updateSeq) { %>
-  -- Update Sequence: <%= updateSeq %>
+-- Update Sequence: <%= updateSeq %>
 <% } %>
+
+<div id="per-page">
+  <label id="per-page" class="drop-down inline">
+    Per page:
+    <select id="select-per-page" name="per-page" class="input-small">
+      <option value="5">5</option>
+      <option value="10">10</option>
+      <option value="20">20</option>
+      <option value="30">30</option>
+      <option value="50">50</option>
+      <option value="100">100</option>
+    </select>
+  </label>
+</div>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/templates/sidebar.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/sidebar.html b/src/fauxton/app/addons/documents/templates/sidebar.html
index 7358960..750cd30 100644
--- a/src/fauxton/app/addons/documents/templates/sidebar.html
+++ b/src/fauxton/app/addons/documents/templates/sidebar.html
@@ -55,8 +55,8 @@ the License.
 
   <nav>
     <ul class="nav nav-list">
-      <li class="active"><a id="all-docs" href="#<%= database.url('index') %>?limit=<%= docLimit %>" class="toggle-view"> All documents</a></li>
-      <li><a id="design-docs" href='#<%= database.url("index") %>?limit=<%= docLimit %>&startkey="_design"&endkey="_e"'  class="toggle-view"> All design docs</a></li>
+      <li class="active"><a id="all-docs" href="#<%= database.url('index') %>" class="toggle-view"> All documents</a></li>
+      <li><a id="design-docs" href='#<%= database.url("index") %>?startkey="_design"&endkey="_e"'  class="toggle-view"> All design docs</a></li>
     </ul>
     <ul class="nav nav-list views">
       <li class="nav-header">Secondary Indexes</li>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/templates/view_editor.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/view_editor.html b/src/fauxton/app/addons/documents/templates/view_editor.html
index 6a20849..e08e36e 100644
--- a/src/fauxton/app/addons/documents/templates/view_editor.html
+++ b/src/fauxton/app/addons/documents/templates/view_editor.html
@@ -17,12 +17,14 @@ the License.
       <% if (newView) { %>Create Index <% } else { %>Edit Index <% } %></a></li>
     <% if (!newView) { %>
     <li><a data-bypass="true" id="query-nav" href="#query" data-toggle="tab">
-      <i class="fonticon-plus fonticon"></i> Query Options</a></li>
+      <i class="fonticon-plus fonticon"></i> Query Options</a>
+    </li>
     <li><a data-bypass="true" id="meta-nav" href="#metadata" data-toggle="tab">Design Doc Metadata</a></li>
     <% } %>
   </ul>
   <div class="all-docs-list errors-container"></div>
   <div class="tab-content">
+	 <div id="query-options-wrapper"></div>
     <div class="tab-pane active" id="index">
       <div id="define-view" class="ddoc-alert well">
         <div class="errors-container"></div>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/tests/resourcesSpec.js b/src/fauxton/app/addons/documents/tests/resourcesSpec.js
index 380a4e4..62506e6 100644
--- a/src/fauxton/app/addons/documents/tests/resourcesSpec.js
+++ b/src/fauxton/app/addons/documents/tests/resourcesSpec.js
@@ -32,20 +32,6 @@ define([
 
     });
 
-    it('Should return urlNext', function () {
-      var url = collection.urlNextPage(20);
-
-      assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=21&reduce=false&startkey_docid=myId2&startkey=');
-
-    });
-
-    it('Should return urlPrevious', function () {
-      var url = collection.urlPreviousPage(20, {limit: 21, reduce: false,  startkey_docid: "myId1",startkey:"myId1"} );
-
-      assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=20&reduce=false&startkey_docid=myId1&startkey=myId1');
-
-    });
-
   });
 
   describe('AllDocs', function () {
@@ -65,19 +51,6 @@ define([
 
     });
 
-    it('Should return urlNext', function () {
-      var url = collection.urlNextPage(20);
-
-      assert.equal(url, 'database/databaseId/_all_docs?limit=21&startkey_docid=%22myId2%22&startkey=%22myId2%22');
-
-    });
-
-     it('Should return urlPrevious', function () {
-      var url = collection.urlPreviousPage(20, {limit: 21, startkey_docid: "myId1",startkey:"myId1"} );
-      assert.equal(url, 'database/databaseId/_all_docs?limit=20&startkey_docid=myId1&startkey=myId1');
-    });
-
-
   });
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js
index 7282ed7..54ec261 100644
--- a/src/fauxton/app/addons/documents/views.js
+++ b/src/fauxton/app/addons/documents/views.js
@@ -26,8 +26,6 @@ define([
        // Plugins
        "plugins/beautify",
        "plugins/prettify",
-
-
 ],
 
 function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColumns, beautify) {
@@ -416,25 +414,38 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
 
     initialize: function (options) {
       this.newView = options.newView || false;
-      this.showNumbers = options.showNumbers;
       this.pagination = options.pagination;
-
+      _.bindAll(this);
+      
+      this._perPage = options.perPageDefault || 20;
       this.listenTo(this.collection, 'totalRows:decrement', this.render);
     },
 
+    events: {
+      'change #select-per-page': 'updatePerPage'
+    },
+
+    updatePerPage: function (event) {
+      this._perPage = parseInt(this.$('#select-per-page :selected').val(), 10);
+      this.pagination.updatePerPage(this.perPage());
+      FauxtonAPI.triggerRouteEvent('perPageChange', this.pagination.documentsLeftToFetch());
+    },
+
+    afterRender: function () {
+      this.$('option[value="' + this.perPage() + '"]').attr('selected', "selected");
+    },
+
     serialize: function () {
        var totalRows = 0,
-          recordStart = 0,
           updateSeq = false,
           pageStart = 0,
           pageEnd = 20;
 
       if (!this.newView) {
-        totalRows = this.collection.totalRows();
+        totalRows = this.collection.length;
         updateSeq = this.collection.updateSeq();
       }
 
-      recordStart = this.collection.recordStart();
       if (this.pagination) {
         pageStart = this.pagination.pageStart();
         pageEnd =  this.pagination.pageEnd();
@@ -443,13 +454,18 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       return {
         database: app.utils.safeURLName(this.collection.database.id),
         updateSeq: updateSeq,
-        offset: recordStart,
         totalRows: totalRows,
-        showNumbers: this.showNumbers,
-        numModels: this.collection.models.length + recordStart - 1,
         pageStart: pageStart,
         pageEnd: pageEnd
       };
+    },
+
+    perPage: function () {
+      return this._perPage;
+    },
+
+    setCollection: function (collection) {
+      this.collection = collection;
     }
 
   });
@@ -468,7 +484,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
 
     toggleQuery: function (event) {
       $('#dashboard-content').scrollTop(0);
-      this.$('#query').toggle('fast');
+      this.$('#query').toggle('slow');
     },
 
     beforeRender: function () {
@@ -477,17 +493,14 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
         previewFn: this.previewView,
         hasReduce: false,
         showPreview: false,
-        database: this.database
+        database: this.database,
       }));
-
-      this.$('#query').hide();
     },
 
     afterRender: function () {
       if (this.params) {
         this.advancedOptions.updateFromParams(this.params);
       }
-
     },
 
     updateAllDocs: function (event, paramInfo) {
@@ -518,9 +531,12 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       }
 
       var fragment = window.location.hash.replace(/\?.*$/, '');
-      fragment = fragment + '?' + $.param(params);
-      FauxtonAPI.navigate(fragment, {trigger: false});
 
+      if (!_.isEmpty(params)) {
+        fragment = fragment + '?' + $.param(params);
+      }
+
+      FauxtonAPI.navigate(fragment, {trigger: false});
       FauxtonAPI.triggerRouteEvent('updateAllDocs', {allDocs: true});
     },
 
@@ -537,7 +553,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       "click button.all": "selectAll",
       "click button.bulk-delete": "bulkDelete",
       "click #collapse": "collapse",
-      "change .row-select":"toggleTrash"
+      "change .row-select":"toggleTrash",
+      "click #js-end-results": "scrollToQuery"
     },
 
     toggleTrash: function () {
@@ -548,26 +565,45 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       }
     },
 
+    scrollToQuery: function () {
+      $('#dashboard-content').animate({ scrollTop: 0 }, 'slow');
+    },
+
     initialize: function(options){
       this.nestedView = options.nestedView || Views.Document;
       this.rows = {};
       this.viewList = !! options.viewList;
       this.database = options.database;
+
       if (options.ddocInfo) {
         this.designDocs = options.ddocInfo.designDocs;
         this.ddocID = options.ddocInfo.id;
       }
       this.newView = options.newView || false;
+      this.docParams = options.docParams;
+      this.params = options.params || {};
       this.expandDocs = true;
+      this.perPageDefault = this.docParams.limit || 20;
     },
 
     establish: function() {
       if (this.newView) { return null; }
 
-      return this.collection.fetch({reset: true}).fail(function() {
-        // TODO: handle error requests that slip through
-        // This should just throw a notification, not break the page
-        console.log("ERROR: ", arguments);
+      return this.collection.fetch({
+        reset: true,
+        success:  function() { },
+        error: function(model, xhr, options){
+          // TODO: handle error requests that slip through
+          // This should just throw a notification, not break the page
+          FauxtonAPI.addNotification({
+            msg: "Bad Request",
+            type: "error"
+          });
+
+          //now redirect back to alldocs
+          FauxtonAPI.navigate(model.database.url("index") + "?limit=100");
+          console.log("ERROR: ", arguments);
+        }
       });
     },
 
@@ -576,16 +612,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
 
     serialize: function() {
-      var requestDuration = false;
-
-      if (this.collection.requestDurationInString) {
-        requestDuration = this.collection.requestDurationInString();
-      }
-
       return {
         viewList: this.viewList,
-        requestDuration: requestDuration,
-        expandDocs: this.expandDocs
+        expandDocs: this.expandDocs,
+        endOfResults: !this.pagination.canShowNextfn()
       };
     },
 
@@ -646,70 +676,41 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
 
     addPagination: function () {
       var collection = this.collection;
-      var perPage = function () {
-        if (collection.params.limit && collection.skipFirstItem) {
-          return parseInt(collection.params.limit, 10) - 1;
-        } else if (collection.params.limit) {
-          return parseInt(collection.params.limit, 10);
-        }
-
-        return 20;
-      };
 
       this.pagination = new Components.IndexPagination({
         collection: this.collection,
         scrollToSelector: '#dashboard-content',
-        previousUrlfn: function () {
-          return collection.urlPreviousPage(perPage(), this.previousParams.pop());
-        },
-        canShowPreviousfn: function () {
-          if (this.previousParams.length === 0) {
-            return false;
-          }
-
-          return true;
-        },
-        canShowNextfn: function () {
-          if (collection.length < (perPage() -1)) {
-            return false;
-          }
-
-          return true;
-        },
-
-        nextUrlfn: function () {
-          return collection.urlNextPage(perPage());
-        }
+        docLimit: this.params.limit,
+        perPage: this.perPageDefault
       });
     },
 
     cleanup: function () {
-      this.allDocsNumber.remove();
+      this.pagination && this.pagination.remove();
+      this.allDocsNumber && this.allDocsNumber.remove();
       _.each(this.rows, function (row) {row.remove();});
-
-      if (!this.pagination) { return; }
-      this.pagination.remove();
     },
 
     beforeRender: function() {
-      var showNumbers = true;
 
       if (!this.pagination) {
         this.addPagination();
       }
 
-      this.insertView('#documents-pagination', this.pagination);
+      if (!this.params.keys) { //cannot paginate with keys
+        this.insertView('#documents-pagination', this.pagination);
+      }
 
-      if (this.designDocs || this.collection.idxType === '_view' || this.collection.params.startkey === '"_design"') {
-        showNumbers = false;
+      if (!this.allDocsNumber) {
+        this.allDocsNumber = new Views.AllDocsNumber({
+          collection: this.collection,
+          newView: this.newView,
+          pagination: this.pagination,
+          perPageDefault: this.perPageDefault
+        });
       }
 
-      this.allDocsNumber = this.setView('#item-numbers', new Views.AllDocsNumber({
-        collection: this.collection,
-        newView: this.newView,
-        showNumbers: showNumbers,
-        pagination: this.pagination
-      }));
+      this.setView('#item-numbers', this.allDocsNumber);
 
       var docs = this.expandDocs ? this.collection : this.collection.simple();
 
@@ -720,8 +721,32 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       }, this);
     },
 
+    setCollection: function (collection) {
+      this.collection = collection;
+      this.pagination.setCollection(collection);
+      this.allDocsNumber.setCollection(collection);
+    },
+
+    setParams: function (docParams, urlParams) {
+      this.docParams = docParams;
+      this.params = urlParams;
+      this.perPageDefault = this.docParams.limit;
+
+      if (this.params.limit) {
+        this.pagination.docLimit = this.params.limit;
+      }
+    },
+
     afterRender: function(){
       prettyPrint();
+    },
+
+    perPage: function () {
+      return this.allDocsNumber.perPage();
+    },
+
+    updatePerPage: function (newPerPage) {
+      this.collection.updateLimit(newPerPage);
     }
   });
 
@@ -942,9 +967,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
           model = this.model;
 
       editor.editor.on("change", function (event) {
-        //if (event.data.action !== 'removeText') { return; }
-        //if (!event.data.text.match(/_id/) && !event.data.text.match(/_rev/)) { return; }
-
         var changedDoc;
         try {
           changedDoc = JSON.parse(editor.getValue());
@@ -1033,7 +1055,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       this.viewName = options.viewName;
       this.updateViewFn = options.updateViewFn;
       this.previewFn = options.previewFn;
-      //this.hadReduce = options.hasReduce || true;
 
       if (typeof(options.hasReduce) === 'undefined') {
         this.hasReduce = true;
@@ -1049,9 +1070,9 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
 
     events: {
-      "change form.view-query-update input": "updateFilters",
-      "change form.view-query-update select": "updateFilters",
-      "submit form.view-query-update": "updateView",
+      "change form.js-view-query-update input": "updateFilters",
+      "change form.js-view-query-update select": "updateFilters",
+      "submit form.js-view-query-update": "updateView",
       "click button.preview": "previewView"
     },
 
@@ -1071,11 +1092,15 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
 
     queryParams: function () {
-      var $form = this.$(".view-query-update");
+      var $form = this.$(".js-view-query-update");
       // Ignore params without a value
-      var params = _.filter($form.serializeArray(), function(param) {
-        return param.value;
-      });
+      var params = _.reduce($form.serializeArray(), function(params, param) {
+        if (!param.value) { return params; }
+        if (param.name === "limit" && param.value === 'None') { return params; }
+
+        params.push(param);
+        return params;
+      }, []);
 
       // Validate *key* params to ensure they're valid JSON
       var keyParams = ["key","keys","startkey","endkey"];
@@ -1103,7 +1128,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
 
     updateFiltersFor: function(name, $ele) {
-      var $form = $ele.parents("form.view-query-update:first");
+      var $form = $ele.parents("form.js-view-query-update:first");
       switch (name) {
         // Reduce constraints
         //   - Can't include_docs for reduce=true
@@ -1131,12 +1156,13 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
 
     updateFromParams: function (params) {
-      var $form = this.$el.find("form.view-query-update");
+      var $form = this.$el.find("form.js-view-query-update");
       _.each(params, function(val, key) {
         var $ele;
         switch (key) {
           case "limit":
-            case "group_level":
+          case "group_level":
+            if (!val) { return; }
             $form.find("select[name='"+key+"']").val(val);
           break;
           case "include_docs":
@@ -1247,7 +1273,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       "click button.preview": "previewView",
       "click #db-views-tabs-nav": 'toggleIndexNav',
       "click .beautify_map":  "beautifyCode",
-      "click .beautify_reduce":  "beautifyCode"
+      "click .beautify_reduce":  "beautifyCode",
+      "click #query-options-wrapper": 'toggleIndexNav'
     },
 
     langTemplates: {
@@ -1368,6 +1395,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
           that.mapEditor.editSaved();
           that.reduceEditor && that.reduceEditor.editSaved();
 
+
           FauxtonAPI.addNotification({
             msg: "View has been saved.",
             type: "success",
@@ -1448,9 +1476,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       }
 
        var fragment = window.location.hash.replace(/\?.*$/, '');
-       fragment = fragment + '?' + $.param(params);
-       FauxtonAPI.navigate(fragment, {trigger: false});
+       if (!_.isEmpty(params)) {
+        fragment = fragment + '?' + $.param(params);
+       }
 
+       FauxtonAPI.navigate(fragment, {trigger: false});
        FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: this.ddocID, view: this.viewName});
     },
 
@@ -1622,18 +1652,25 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
         database: this.database
       }));
 
-      this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
-        updateViewFn: this.updateView,
-        previewFn: this.previewView,
-        database: this.database,
-        viewName: this.viewName,
-        ddocName: this.model.id,
-        hasReduce: this.hasReduce()
-      }));
+
+      if (!this.newView) {
+        this.eventer = _.extend({}, Backbone.Events);
+
+        this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
+          updateViewFn: this.updateView,
+          previewFn: this.previewView,
+          database: this.database,
+          viewName: this.viewName,
+          ddocName: this.model.id,
+          hasReduce: this.hasReduce(),
+          eventer: this.eventer
+        }));
+      }
+
     },
 
     afterRender: function() {
-      if (this.params) {
+      if (this.params && !this.newView) {
         this.advancedOptions.updateFromParams(this.params);
       }
 
@@ -1646,7 +1683,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
         this.$('#index-nav').parent().removeClass('active');
       }
 
-
     },
 
     showEditors: function () {
@@ -1737,11 +1773,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
       return {
         changes_url: '#' + this.database.url('changes'),
         permissions_url: '#' + this.database.url('app') + '/permissions',
-        db_url: '#' + this.database.url('index') + '?limit=' + Databases.DocLimit,
+        db_url: '#' + this.database.url('index'),
         database: this.collection.database,
         database_url: '#' + this.database.url('app'),
         docLinks: docLinks,
-        docLimit: Databases.DocLimit,
         addLinks: addLinks,
         newLinks: newLinks,
         extensionList: extensionList > 0

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/base.js b/src/fauxton/app/addons/fauxton/base.js
index 35babb5..a6e462a 100644
--- a/src/fauxton/app/addons/fauxton/base.js
+++ b/src/fauxton/app/addons/fauxton/base.js
@@ -45,7 +45,6 @@ function(app, FauxtonAPI, resizeColumns) {
     }
   });
 
-
   Fauxton.initialize = function () {
     app.footer = new Fauxton.Footer({el: "#footer-content"}),
     app.navBar = new Fauxton.NavBar();
@@ -93,7 +92,7 @@ function(app, FauxtonAPI, resizeColumns) {
     });
   };
 
-  Fauxton.Breadcrumbs = Backbone.View.extend({
+  Fauxton.Breadcrumbs = FauxtonAPI.View.extend({
     template: "templates/fauxton/breadcrumbs",
 
     serialize: function() {
@@ -114,10 +113,7 @@ function(app, FauxtonAPI, resizeColumns) {
     }
   });
 
-  // TODO: this View should extend from FauxtonApi.View.
-  // Chicken and egg problem, api.js extends fauxton/base.js.
-  // Need to sort the loading order.
-  Fauxton.Footer = Backbone.View.extend({
+  Fauxton.Footer = FauxtonAPI.View.extend({
     template: "templates/fauxton/footer",
 
     initialize: function() {
@@ -135,7 +131,7 @@ function(app, FauxtonAPI, resizeColumns) {
     }
   });
 
-  Fauxton.NavBar = Backbone.View.extend({
+  Fauxton.NavBar = FauxtonAPI.View.extend({
     className:"navbar",
     template: "templates/fauxton/nav_bar",
     // TODO: can we generate this list from the router?
@@ -260,7 +256,7 @@ function(app, FauxtonAPI, resizeColumns) {
     // TODO: ADD ACTIVE CLASS
   });
 
-  Fauxton.ApiBar = Backbone.View.extend({
+  Fauxton.ApiBar = FauxtonAPI.View.extend({
     template: "templates/fauxton/api_bar",
     endpoint: '_all_docs',
 
@@ -304,7 +300,7 @@ function(app, FauxtonAPI, resizeColumns) {
 
   });
 
-  Fauxton.Notification = Backbone.View.extend({
+  Fauxton.Notification = FauxtonAPI.View.extend({
     fadeTimer: 5000,
 
     initialize: function(options) {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/fauxton/components.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/components.js b/src/fauxton/app/addons/fauxton/components.js
index 0422b5a..7dcf2d7 100644
--- a/src/fauxton/app/addons/fauxton/components.js
+++ b/src/fauxton/app/addons/fauxton/components.js
@@ -69,19 +69,68 @@ function(app, FauxtonAPI, ace, spin) {
     initialize: function (options) {
       this.previousUrlfn = options.previousUrlfn;
       this.nextUrlfn = options.nextUrlfn;
-      this.canShowPreviousfn = options.canShowPreviousfn;
-      this.canShowNextfn = options.canShowNextfn;
       this.scrollToSelector = options.scrollToSelector;
       _.bindAll(this);
-      this.previousParams = [];
+      this.docLimit = options.docLimit || 1000000;
+      this.perPage = options.perPage || 20;
+      this.setDefaults();
+    },
+
+    setDefaults: function () {
+      this._pageNumber = [];
+      this._pageStart = 1;
+      this.enabled = true;
+      this.currentPage = 1;
+    },
+
+    canShowPreviousfn: function () {
+      if (this._pageStart === 1 || !this.enabled) {
+        return false;
+      }
+      return true;
+    },
+
+    canShowNextfn: function () {
+      if (!this.enabled) { return this.enabled; }
+
+      if (this.collection.length < (this.perPage -1)) {
+        return false;
+      }
+
+      if ((this.pageStart() + this.perPage) >= this.docLimit) {
+        return false;
+      }
+
+      if (this.collection.viewMeta && this.collection.viewMeta.total_rows <= this.pageStart() + this.perPage) {
+        return false;
+      }
+
+      return true;
     },
 
     previousClicked: function (event) {
       event.preventDefault();
       event.stopPropagation();
       if (!this.canShowPreviousfn()) { return; }
-      FauxtonAPI.navigate(this.previousUrlfn(), {trigger: false});
-      FauxtonAPI.triggerRouteEvent('paginate', 'previous');
+
+      this.decPageNumber();
+
+      FauxtonAPI.triggerRouteEvent('paginate', {
+       direction: 'previous',
+       perPage: this.perPage,
+       currentPage: this.currentPage
+      });
+    },
+
+    documentsLeftToFetch: function () {
+      var documentsLeftToFetch = this.docLimit - this.totalDocsViewed(),
+          limit = this.perPage;
+
+      if (documentsLeftToFetch < this.perPage ) {
+        limit = documentsLeftToFetch;
+      }
+
+      return limit;
     },
 
     nextClicked: function (event) {
@@ -89,14 +138,14 @@ function(app, FauxtonAPI, ace, spin) {
       event.stopPropagation();
       if (!this.canShowNextfn()) { return; }
 
-      var params = _.clone(this.collection.params);
+      this.incPageNumber();
 
-      if (params) {
-        this.previousParams.push(params);
-      }
+      FauxtonAPI.triggerRouteEvent('paginate', {
+       direction: 'next',
+       perPage: this.documentsLeftToFetch(),
+       currentPage: this.currentPage
+      });
 
-      FauxtonAPI.navigate(this.nextUrlfn(), {trigger: false});
-      FauxtonAPI.triggerRouteEvent('paginate', 'next');
     },
 
     serialize: function () {
@@ -106,30 +155,58 @@ function(app, FauxtonAPI, ace, spin) {
       };
     },
 
-    pageLimit: function () {
-      var limit = 20;
+    updatePerPage: function (newPerPage) {
+      this.setDefaults();
+      this.perPage = newPerPage;
+    },
+
+    page: function () {
+      return this._pageStart - 1;
+    },
+
+    incPageNumber: function () {
+      this.currentPage = this.currentPage + 1;
+      this._pageNumber.push({perPage: this.perPage});
+      this._pageStart = this._pageStart + this.perPage;
+    },
+
+    totalDocsViewed: function () {
+      return _.reduce(this._pageNumber, function (total, value) {
+        return total + value.perPage;
+      }, 0);
+    },
 
-      if (this.collection.params.limit && this.collection.skipFirstItem) {
-        limit = parseInt(this.collection.params.limit, 10) - 1;
-      } else if (this.collection.params.limit) {
-        limit = parseInt(this.collection.params.limit, 10);
+    decPageNumber: function () {
+      this.currentPage = this.currentPage - 1;
+      this._pageNumber.pop();
+      var val = this._pageStart - this.perPage;
+      if (val < 1) {
+        val = 1;
       }
 
-      return limit;
+      this._pageStart = val;
     },
 
     pageStart: function () {
-      return (this.previousParams.length * this.pageLimit()) + 1;
-
+      return this._pageStart;
     },
 
     pageEnd: function () {
-      if (this.collection.length < this.pageLimit()) {
-        return (this.previousParams.length * this.pageLimit()) + this.collection.length;
-      }
+      return this.page() + this.collection.length;
+    },
 
-      return (this.previousParams.length * this.pageLimit()) + this.pageLimit();
-    }
+    disable: function () {
+      this.enabled = false;
+    },
+
+    enable: function () {
+      this.enabled = true;
+    },
+
+    setCollection: function (collection) {
+      this.collection = collection;
+      this.setDefaults();
+    },
 
   });
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/tests/paginateSpec.js b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
index 535e26f..8fc409a 100644
--- a/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
+++ b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
@@ -58,15 +58,6 @@ define([
         //FauxtonAPI.navigate.restore && FauxtonAPI.navigate.restore(); 
       });
 
-      it('Should navigate', function () {
-        var navigateMock = sinon.spy(FauxtonAPI, 'navigate');
-
-        paginate.$('a#next').click();
-
-        assert.ok(navigateMock.calledOnce);
-        FauxtonAPI.navigate.restore();
-      });
-
       it('Should trigger routeEvent', function () {
         var navigateMock = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
 
@@ -81,15 +72,6 @@ define([
 
     describe('#previous', function () {
 
-      it('Should navigate', function () {
-        var navigateMock = sinon.spy(FauxtonAPI, 'navigate');
-
-        paginate.$('a#previous').click();
-
-        assert.ok(navigateMock.calledOnce);
-        FauxtonAPI.navigate.restore();
-      });
-
       it('Should trigger routeEvent', function () {
         var navigateMock = sinon.spy(FauxtonAPI, 'triggerRouteEvent');