couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d..@apache.org
Subject [10/14] couchdb commit: updated refs/heads/1.6.x to 6529f77
Date Fri, 11 Apr 2014 19:15:08 GMT
Fauxton: Improved pagination

This is a new version of pagination in Fauxton using skip. It uses a
PagingCollection that has the main algorithm for pagination and exposes
a nice api.

This is an intermediate step as this is a much better pagination than we
have at the moment. However using just skip for pagination is not
optimal as there are two cases where skip pagination fails - For very
large skips and for when documents that a user have paginated past have
been deleted.

The next step once this has landed will be to add in a startkey_docid
pagination as well. The PagingCollection would then decided which method
to use to paginate for an index.


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

Branch: refs/heads/1.6.x
Commit: bbcd98bbfd47fa8317bba71378911b120f265bd2
Parents: fbc0545
Author: Garren Smith <garren.smith@gmail.com>
Authored: Thu Mar 20 09:46:59 2014 +0200
Committer: Garren Smith <garren.smith@gmail.com>
Committed: Thu Apr 10 12:04:52 2014 +0200

----------------------------------------------------------------------
 LICENSE                                         | 221 ++++++++++++++++--
 NOTICE                                          |   4 +-
 src/Makefile.am                                 |   1 +
 src/fauxton/app/addons/databases/views.js       |   2 +-
 src/fauxton/app/addons/documents/resources.js   | 181 ++-------------
 src/fauxton/app/addons/documents/routes.js      |  81 +++----
 .../documents/templates/advanced_options.html   |   5 +-
 src/fauxton/app/addons/documents/views.js       |  14 +-
 src/fauxton/app/addons/fauxton/components.js    |  16 +-
 src/fauxton/app/config.js                       |   3 +-
 .../js/plugins/cloudant.pagingcollection.js     | 224 +++++++++++++++++++
 11 files changed, 492 insertions(+), 260 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index 21cfb8c..4c58f19 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1105,26 +1105,207 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 
-for src/fauxton/asserts/js/plugins/backbone.fetch-cache.js
+for src/fauxton/assets/js/plugins/cloudant.pagingcollection.js
 
-The MIT License (MIT)
-
-Copyright (c) 2012-2013 Andrew Appleton, http://floatleft.com
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/NOTICE
----------------------------------------------------------------------
diff --git a/NOTICE b/NOTICE
index 888acd2..08e3b82 100644
--- a/NOTICE
+++ b/NOTICE
@@ -186,6 +186,6 @@ This product also includes the following third-party components:
 
    Copyright (c) 2010, Ajax.org B.V.
 
- * src/fauxton/asserts/js/plugins/backbone.fetch-cache.js
+ * src/fauxton/asserts/js/plugins/cloudant.pagingcollection.js
 
-   Copyright (c) 2012-2013 Andrew Appleton, http://floatleft.com
+   Copyright (c) 2014, Cloudant http://cloudant.com

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index e1007f9..07ee5e1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -257,6 +257,7 @@ FAUXTON_FILES = \
     fauxton/assets/js/libs/require.js \
     fauxton/assets/js/libs/spin.min.js \
     fauxton/assets/js/plugins/backbone.layoutmanager.js \
+    fauxton/assets/js/plugins/cloudant.pagingcollection.js \
     fauxton/assets/js/plugins/jquery.form.js \
     fauxton/assets/js/plugins/prettify.js \
     fauxton/assets/js/plugins/beautify.js\

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/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 d632486..0806b92 100644
--- a/src/fauxton/app/addons/databases/views.js
+++ b/src/fauxton/app/addons/databases/views.js
@@ -81,7 +81,7 @@ function(app, Components, FauxtonAPI, Databases) {
           // TODO: switch to using a model, or Databases.databaseUrl()
           // Neither of which are in scope right now
           // var db = new Database.Model({id: dbname});
-          var url = ["/database/", app.utils.safeURLName(dbname), "/_all_docs?limit=" + Databases.DocLimit].join('');
+          var url = ["/database/", app.utils.safeURLName(dbname), "/_all_docs"].join('');
           FauxtonAPI.navigate(url);
       } else {
         FauxtonAPI.addNotification({

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/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 e47bd61..a787f0d 100644
--- a/src/fauxton/app/addons/documents/resources.js
+++ b/src/fauxton/app/addons/documents/resources.js
@@ -12,10 +12,11 @@
 
 define([
   "app",
-  "api"
+  "api",
+  "cloudant.pagingcollection"
 ],
 
-function(app, FauxtonAPI) {
+function(app, FauxtonAPI, PagingCollection) {
   var Documents = FauxtonAPI.addon();
 
   Documents.QueryParams = (function () {
@@ -40,70 +41,7 @@ function(app, FauxtonAPI) {
     };
   })();
 
-  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(){
@@ -357,25 +295,8 @@ function(app, FauxtonAPI) {
 
   });
 
-  var DefaultParametersMixin = function() {
-    // keep this variable private
-    var defaultParams;
-
-    return {
-      saveDefaultParameters: function() {
-        // store the default parameters so we can reset to the first page
-        defaultParams = _.clone(this.params);
-      },
-
-      restoreDefaultParameters: function() {
-        this.params = _.clone(defaultParams);
-      }
-    };
-  };
-
-  Documents.AllDocs = FauxtonAPI.Collection.extend(_.extend({}, DefaultParametersMixin(),
{
+  Documents.AllDocs = PagingCollection.extend({
     model: Documents.Doc,
-    isAllDocs: true,
     documentation: function(){
       return "docs";
     },
@@ -389,11 +310,9 @@ function(app, FauxtonAPI) {
       if (!this.params.limit) {
         this.params.limit = this.perPageLimit;
       }
-
-      this.saveDefaultParameters();
     },
 
-    url: function(context, params) {
+    urlRef: function(context, params) {
       var query = "";
 
       if (params) {
@@ -415,6 +334,10 @@ function(app, FauxtonAPI) {
       }
     },
 
+    url: function () {
+      return this.urlRef.apply(this, arguments);
+    },
+
     simple: function () {
       var docs = this.map(function (item) {
         return {
@@ -429,15 +352,6 @@ function(app, FauxtonAPI) {
       });
     },
 
-    updateLimit: function (limit) {
-      this.perPageLimit = limit;
-      this.params.limit = limit;
-    },
-
-    updateParams: function (params) {
-      this.params = params;
-    },
-
     totalRows: function() {
       return this.viewMeta.total_rows || "unknown";
     },
@@ -456,37 +370,17 @@ function(app, FauxtonAPI) {
     parse: function(resp) {
       var rows = resp.rows;
 
-      this.viewMeta = {
-        total_rows: resp.total_rows,
-        offset: resp.offset,
-        update_seq: resp.update_seq
-      };
-
-      //Paginating, don't show first item as it was the last
-      //item in the previous page
-      if (this.skipFirstItem) {
-        rows = rows.splice(1);
-      }
-
       // remove any query errors that may return without doc info
       // important for when querying keys on all docs
-      var noQueryErrors = _.filter(rows, function(row){
+      resp.rows = _.filter(rows, function(row){
         return row.value;
       });
 
-      return _.map(noQueryErrors, function(row) {
-          return {
-            _id: row.id,
-            _rev: row.value.rev,
-            value: row.value,
-            key: row.key,
-            doc: row.doc || undefined
-          };
-      });
+      return PagingCollection.prototype.parse.call(this, resp);
     }
-  }));
+  });
 
-  Documents.IndexCollection = FauxtonAPI.Collection.extend(_.extend({}, DefaultParametersMixin(),
{
+  Documents.IndexCollection = PagingCollection.extend({
     model: Documents.ViewRow,
     documentation: function(){
       return "docs";
@@ -498,17 +392,14 @@ function(app, FauxtonAPI) {
       this.idxType = "_view";
       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;
       }
-
-      this.saveDefaultParameters();
     },
 
-    url: function(context, params) {
+    urlRef: function(context, params) {
       var query = "";
       if (params) {
         if (!_.isEmpty(params)) {
@@ -533,18 +424,8 @@ function(app, FauxtonAPI) {
       return url.join("/") + query;
     },
 
-    updateParams: function (params) {
-      this.params = params;
-    },
-
-    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 = limit;
+    url: function () {
+      return this.urlRef.apply(this, arguments);
     },
 
     totalRows: function() {
@@ -579,23 +460,7 @@ function(app, FauxtonAPI) {
       this.endTime = new Date().getTime();
       this.requestDuration = (this.endTime - this.startTime);
 
-      if (this.skipFirstItem) {
-        rows = rows.splice(1);
-      }
-
-      this.viewMeta = {
-        total_rows: resp.total_rows,
-        offset: resp.offset,
-        update_seq: resp.update_seq
-      };
-      return _.map(rows, function(row) {
-        return {
-          value: row.value,
-          key: row.key,
-          doc: row.doc,
-          id: row.id
-        };
-      });
+      return PagingCollection.prototype.parse.apply(this, arguments);
     },
 
     buildAllDocs: function(){
@@ -606,7 +471,7 @@ function(app, FauxtonAPI) {
     // we can get the request duration
     fetch: function () {
       this.startTime = new Date().getTime();
-      return FauxtonAPI.Collection.prototype.fetch.call(this);
+      return PagingCollection.prototype.fetch.call(this);
     },
 
     allDocs: function(){
@@ -645,10 +510,10 @@ function(app, FauxtonAPI) {
       return timeString;
     }
 
-  }));
+  });
 
 
-  Documents.PouchIndexCollection = FauxtonAPI.Collection.extend(_.extend({}, DefaultParametersMixin(),
{
+  Documents.PouchIndexCollection = PagingCollection.extend({
     model: Documents.ViewRow,
     documentation: function(){
       return "docs";
@@ -661,8 +526,6 @@ function(app, FauxtonAPI) {
       this.params = _.extend({limit: 20, reduce: false}, options.params);
 
       this.idxType = "_view";
-
-      this.saveDefaultParameters();
     },
 
     url: function () {
@@ -717,7 +580,7 @@ function(app, FauxtonAPI) {
     allDocs: function(){
       return this.models;
     }
-  }));
+  });
 
 
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/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 699a496..5e8834f 100644
--- a/src/fauxton/app/addons/documents/routes.js
+++ b/src/fauxton/app/addons/documents/routes.js
@@ -168,10 +168,14 @@ function(app, FauxtonAPI, Documents, Databases) {
 
       this.data.designDocs = new Documents.AllDocs(null, {
         database: this.data.database,
+        paging: {
+          pageSize: 500
+        },
         params: {
-          startkey: '"_design"',
-          endkey: '"_design1"',
-          include_docs: true
+          startkey: '_design',
+          endkey: '_design1',
+          include_docs: true,
+          limit: 500
         }
       });
 
@@ -182,11 +186,11 @@ function(app, FauxtonAPI, Documents, Databases) {
     },
 
     establish: function () {
-      return this.data.designDocs.fetch();
+      return this.data.designDocs.fetch({reset: true});
     },
 
     createParams: function (options) {
-      var urlParams = app.getParams(options);
+      var urlParams = Documents.QueryParams.parse(app.getParams(options));
       return {
         urlParams: urlParams,
         docParams: _.extend(_.clone(urlParams), {limit: this.getDocPerPageLimit(urlParams,
20)})
@@ -223,6 +227,8 @@ function(app, FauxtonAPI, Documents, Databases) {
         collection: this.data.database.allDocs
       }));
 
+      this.data.database.allDocs.paging.pageSize = this.getDocPerPageLimit(urlParams, parseInt(docParams.limit,
10));
+
       this.setView("#dashboard-upper-content", new Documents.Views.AllDocsLayout({
         database: this.data.database,
         collection: this.data.database.allDocs,
@@ -240,9 +246,7 @@ function(app, FauxtonAPI, Documents, Databases) {
         {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)}
       ];
 
-      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();
+      this.apiUrl = [this.data.database.allDocs.urlRef("apiurl", urlParams), this.data.database.allDocs.documentation()
];
     },
 
     viewFn: function (databaseName, ddoc, view) {
@@ -257,7 +261,10 @@ function(app, FauxtonAPI, Documents, Databases) {
         database: this.data.database,
         design: decodeDdoc,
         view: view,
-        params: docParams
+        params: docParams,
+        paging: {
+          pageSize: this.getDocPerPageLimit(urlParams, parseInt(docParams.limit, 10))
+        }
       });
      
       this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
@@ -290,8 +297,7 @@ function(app, FauxtonAPI, Documents, Databases) {
         ];
       };
 
-      this.apiUrl = [this.data.indexedDocs.url("apiurl", urlParams), "docs"];
-      Documents.paginate.reset();
+      this.apiUrl = [this.data.indexedDocs.urlRef("apiurl", urlParams), "docs"];
     },
 
     ddocInfo: function (designDoc, designDocs, view) {
@@ -344,22 +350,27 @@ function(app, FauxtonAPI, Documents, Databases) {
           urlParams = params.urlParams,
           docParams = params.docParams,
           ddoc = event.ddoc,
+          pageSize,
           collection;
 
-      docParams.limit = this.getDocPerPageLimit(urlParams, this.documentsView.perPage());
+      docParams.limit = pageSize = this.getDocPerPageLimit(urlParams, this.documentsView.perPage());
       this.documentsView.forceRender();
 
       if (event.allDocs) {
         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;
+        collection.paging.pageSize = pageSize;
 
       } else {
         collection = this.data.indexedDocs = new Documents.IndexCollection(null, {
           database: this.data.database,
           design: ddoc,
           view: view,
-          params: docParams
+          params: docParams,
+          paging: {
+            pageSize: pageSize
+          }
         });
 
         if (!this.documentsView) {
@@ -378,8 +389,7 @@ function(app, FauxtonAPI, Documents, Databases) {
       this.documentsView.setCollection(collection);
       this.documentsView.setParams(docParams, urlParams);
 
-      this.apiUrl = [collection.url("apiurl", urlParams), "docs"];
-      Documents.paginate.reset();
+      this.apiUrl = [collection.urlRef("apiurl", urlParams), "docs"];
     },
 
     updateAllDocsFromPreview: function (event) {
@@ -405,47 +415,18 @@ function(app, FauxtonAPI, Documents, Databases) {
     perPageChange: function (perPage) {
       // We need to restore the collection parameters to the defaults (1st page)
       // and update the page size
-      var params = this.documentsView.collection.restoreDefaultParameters();
       this.perPage = perPage;
-      this.documentsView.updatePerPage(perPage);
       this.documentsView.forceRender();
-      this.documentsView.collection.params.limit = perPage;
+      this.documentsView.collection.pageSizeReset(perPage, {fetch: false});
       this.setDocPerPageLimit(perPage);
     },
 
     paginate: function (options) {
-      var params = {},
-          urlParams = app.getParams(),
-          collection = this.documentsView.collection;
+      var collection = this.documentsView.collection;
 
       this.documentsView.forceRender();
-
-      // 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 {
-          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);
+      collection.paging.pageSize = options.perPage;
+      var promise = collection[options.direction]({fetch: false});
     },
 
     reloadDesignDocs: function (event) {
@@ -476,9 +457,9 @@ function(app, FauxtonAPI, Documents, Databases) {
       } 
 
       if (!urlParams.limit || urlParams.limit > storedPerPage) {
-        return storedPerPage;
+        return parseInt(storedPerPage, 10);
       } else {
-        return urlParams.limit;
+        return parseInt(urlParams.limit, 10);
       }
     }
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/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 d8d57cd..55c5946 100644
--- a/src/fauxton/app/addons/documents/templates/advanced_options.html
+++ b/src/fauxton/app/addons/documents/templates/advanced_options.html
@@ -50,13 +50,14 @@ the License.
       <label class="drop-down inline">
         Limit:
         <select name="limit" class="input-small">
+          <option selected="selected">None</option>
           <option>5</option>
           <option>10</option>
-          <option selected="selected">20</option>
+          <option>20</option>
           <option>30</option>
           <option>50</option>
           <option>100</option>
-		  <option>500</option>
+		      <option>500</option>
         </select>
       </label>
       <label for="skipRows" class="inline drop-down">

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/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 cc23e19..351b2b0 100644
--- a/src/fauxton/app/addons/documents/views.js
+++ b/src/fauxton/app/addons/documents/views.js
@@ -681,8 +681,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
 
     addPagination: function () {
-      var collection = this.collection;
-
       this.pagination = new Components.IndexPagination({
         collection: this.collection,
         scrollToSelector: '#dashboard-content',
@@ -703,9 +701,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
         this.addPagination();
       }
 
-      if (!this.params.keys) { //cannot paginate with keys
-        this.insertView('#documents-pagination', this.pagination);
-      }
+      this.insertView('#documents-pagination', this.pagination);
 
       if (!this.allDocsNumber) {
         this.allDocsNumber = new Views.AllDocsNumber({
@@ -749,10 +745,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
resizeColum
 
     perPage: function () {
       return this.allDocsNumber.perPage();
-    },
-
-    updatePerPage: function (newPerPage) {
-      this.collection.updateLimit(newPerPage);
     }
   });
 
@@ -1741,9 +1733,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
resizeColum
         this.ddocID = this.model.id;
       } else {
         var ddocDecode = decodeURIComponent(this.ddocID);
-        this.model = this.ddocs.get(ddocDecode).dDocModel();
+        this.model = this.ddocs.get(this.ddocID).dDocModel();
         this.reduceFunStr = this.model.viewHasReduce(this.viewName);
-        
       }
 
       this.designDocSelector = this.setView('.design-doc-group', new Views.DesignDocSelector({
@@ -1752,7 +1743,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb,
resizeColum
         database: this.database
       }));
 
-
       if (!this.newView) {
         this.eventer = _.extend({}, Backbone.Events);
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/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 25f623c..47f4726 100644
--- a/src/fauxton/app/addons/fauxton/components.js
+++ b/src/fauxton/app/addons/fauxton/components.js
@@ -84,28 +84,18 @@ function(app, FauxtonAPI, ace, spin) {
     },
 
     canShowPreviousfn: function () {
-      if (this._pageStart === 1 || !this.enabled) {
-        return false;
-      }
-      return true;
+      if (!this.enabled) { return this.enabled; }
+      return this.collection.hasPrevious();
     },
 
     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;
+      return this.collection.hasNext();
     },
 
     previousClicked: function (event) {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/src/fauxton/app/config.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/config.js b/src/fauxton/app/config.js
index 4a2f136..edcd9a2 100644
--- a/src/fauxton/app/config.js
+++ b/src/fauxton/app/config.js
@@ -30,7 +30,8 @@ require.config({
     spin: "../assets/js/libs/spin.min",
     d3: "../assets/js/libs/d3",
     "nv.d3": "../assets/js/libs/nv.d3",
-    "ace":"../assets/js/libs/ace"
+    "ace":"../assets/js/libs/ace",
+    "cloudant.pagingcollection": "../assets/js/plugins/cloudant.pagingcollection"
   },
 
   baseUrl: '/',

http://git-wip-us.apache.org/repos/asf/couchdb/blob/bbcd98bb/src/fauxton/assets/js/plugins/cloudant.pagingcollection.js
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/js/plugins/cloudant.pagingcollection.js b/src/fauxton/assets/js/plugins/cloudant.pagingcollection.js
new file mode 100644
index 0000000..2ab5eaf
--- /dev/null
+++ b/src/fauxton/assets/js/plugins/cloudant.pagingcollection.js
@@ -0,0 +1,224 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+(function(root, factory) {
+  "use strict";
+  // start with AMD, so paginate could then be used by ``var paginate = require('paginate');``
+  if (typeof define === 'function' && define.amd) {
+    define(['underscore', 'backbone', 'jquery'], function(_, Backbone, $) {
+      // Export global even in AMD case in case this script is loaded with
+      // others that may still expect a global paginate.
+      return factory(root, null, _, Backbone, $.param);
+    });
+
+  // Next check for Node.js or CommonJS. Also look to see if either
+  // underscore or lodash are the modules being used
+  } else if (typeof exports !== 'undefined') {
+    var Backbone = require('Backbone'),
+        $param = require('querystring').stringify,
+        _;
+    try {
+      _ = require('underscore');
+    } catch(e) {
+      _ = require('lodash');
+    }
+    factory(root, exports, _, Backbone, $param);
+
+  // Finally, register as a browser global.
+  } else {
+    root.PagingCollection = factory(root, {}, root._, root.Backbone, root.$.param);
+  }
+
+}(this, function(root, exports, _, Backbone, $param) {
+  "use strict";
+
+  //PagingCollection
+  //----------------
+
+  // A PagingCollection knows how to build appropriate requests to the
+  // CouchDB-like server and how to fetch. The Collection will always contain a
+  // single page of documents.
+
+  var PagingCollection = Backbone.Collection.extend({
+
+    // initialize parameters and page size
+    constructor: function() {
+      Backbone.Collection.apply(this, arguments);
+      this.configure.apply(this, arguments);
+    },
+
+    configure: function(collections, options) {
+      var querystring = _.result(this, "url").split("?")[1] || "";
+      this.paging = _.defaults((options.paging || {}), {
+        defaultParams: _.defaults({}, options.params, this._parseQueryString(querystring)),
+        hasNext: false,
+        hasPrevious: false,
+        params: {},
+        pageSize: 20,
+        direction: undefined
+      });
+
+      this.paging.params = _.clone(this.paging.defaultParams);
+      this.updateUrlQuery(this.paging.defaultParams);
+    },
+
+    calculateParams: function(currentParams, skipIncrement, limitIncrement) {
+
+      var params = _.clone(currentParams);
+      params.skip = (parseInt(currentParams.skip, 10) || 0) + skipIncrement;
+
+      // guard against hard limits
+      if(this.paging.defaultParams.limit) {
+        params.limit = Math.min(this.paging.defaultParams.limit, params.limit);
+      }
+      // request an extra row so we know that there are more results
+      params.limit = limitIncrement + 1;
+      // prevent illegal skip values
+      params.skip = Math.max(params.skip, 0);
+
+      return params;
+    },
+
+    pageSizeReset: function(pageSize, opts) {
+      var options = _.defaults((opts || {}), {fetch: true});
+      this.paging.direction = undefined;
+      this.paging.pageSize = pageSize;
+      this.paging.params = this.paging.defaultParams;
+      this.paging.params.limit = pageSize;
+      this.updateUrlQuery(this.paging.params);
+      if (options.fetch) {
+        return this.fetch();
+      }
+    },
+
+    _parseQueryString: function(uri) {
+      var queryString = decodeURI(uri).split(/&/);
+
+      return _.reduce(queryString, function (parsedQuery, item) {
+          var nameValue = item.split(/=/);
+          if (nameValue.length === 2) {
+            parsedQuery[nameValue[0]] = nameValue[1];
+          }
+
+          return parsedQuery;
+      }, {});
+    },
+
+    _iterate: function(offset, opts) {
+      var options = _.defaults((opts || {}), {fetch: true});
+
+      this.paging.params = this.calculateParams(this.paging.params, offset, this.paging.pageSize);
+
+      // Fetch the next page of documents
+      this.updateUrlQuery(this.paging.params);
+      if (options.fetch) {
+        return this.fetch({reset: true});
+      }
+    },
+
+    // `next` is called with the number of items for the next page.
+    // It returns the fetch promise.
+    next: function(options){
+      this.paging.direction = "next";
+      return this._iterate(this.paging.pageSize, options);
+    },
+
+    // `previous` is called with the number of items for the previous page.
+    // It returns the fetch promise.
+    previous: function(options){
+      this.paging.direction = "previous";
+      return this._iterate(0 - this.paging.pageSize, options);
+    },
+
+    shouldStringify: function (val) {
+      try {
+        JSON.parse(val);
+        return false;
+      } catch(e) {
+        return true;
+      }
+    },
+
+    // Encodes the parameters so that couchdb will understand them
+    // and then sets the url with the new url.
+    updateUrlQuery: function (params) {
+      var url = _.result(this, "url").split("?")[0];
+
+      _.each(['startkey', 'endkey', 'key'], function (key) {
+        if (_.has(params, key) && this.shouldStringify(params[key])) {
+          params[key] = JSON.stringify(params[key]);
+        }
+      }, this);
+
+      this.url = url + '?' + $param(params);
+    },
+
+    fetch: function () {
+      // if this is a fetch for the first time, fetch one extra to see if there is a next
+      if (!this.paging.direction && this.paging.params.limit > 0) {
+        this.paging.direction = 'fetch';
+        this.paging.params.limit = this.paging.params.limit + 1;
+        this.updateUrlQuery(this.paging.params);
+      }
+
+      return Backbone.Collection.prototype.fetch.apply(this, arguments);
+    },
+
+    parse: function (resp) {
+      var rows = resp.rows;
+
+      this.paging.hasNext = this.paging.hasPrevious = false;
+
+      this.viewMeta = {
+        total_rows: resp.total_rows,
+        offset: resp.offset,
+        update_seq: resp.update_seq
+      };
+
+      var skipLimit = this.paging.defaultParams.skip || 0;
+      if(this.paging.params.skip > skipLimit) {
+        this.paging.hasPrevious = true;
+      }
+
+      if(rows.length === this.paging.pageSize + 1) {
+        this.paging.hasNext = true;
+
+        // remove the next page marker result
+        rows.pop();
+        this.viewMeta.total_rows = this.viewMeta.total_rows - 1;
+      }
+      return rows;
+    },
+
+    hasNext: function() {
+      return this.paging.hasNext;
+    },
+
+    hasPrevious: function() {
+      return this.paging.hasPrevious;
+    }
+  });
+
+
+  if (exports) {
+    // Overload the Backbone.ajax method, this allows PagingCollection to be able to
+    // work in node.js
+    exports.setAjax = function (ajax) {
+      Backbone.ajax = ajax;
+    };
+
+    exports.PagingCollection = PagingCollection;
+  }
+
+  return PagingCollection;
+}));
+


Mime
View raw message