You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2014/03/06 10:46:22 UTC

[1/2] Fauxton: Improved pagination

Repository: couchdb
Updated Branches:
  refs/heads/master 0782a44ce -> 4e60f0b7a


http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/test/mocha/chai.js
----------------------------------------------------------------------
diff --git a/src/fauxton/test/mocha/chai.js b/src/fauxton/test/mocha/chai.js
index 2a67f98..9dd7b0a 100644
--- a/src/fauxton/test/mocha/chai.js
+++ b/src/fauxton/test/mocha/chai.js
@@ -27,10 +27,14 @@ function require(path, parent, orig) {
   // perform real require()
   // by invoking the module's
   // registered function
-  if (!module.exports) {
-    module.exports = {};
-    module.client = module.component = true;
-    module.call(this, module.exports, require.relative(resolved), module);
+  if (!module._resolving && !module.exports) {
+    var mod = {};
+    mod.exports = {};
+    mod.client = mod.component = true;
+    module._resolving = true;
+    module.call(this, mod.exports, require.relative(resolved), mod);
+    delete module._resolving;
+    module.exports = mod.exports;
   }
 
   return module.exports;
@@ -309,6 +313,411 @@ AssertionError.prototype.toJSON = function (stack) {
 };
 
 });
+require.register("chaijs-type-detect/lib/type.js", function(exports, require, module){
+/*!
+ * type-detect
+ * Copyright(c) 2013 jake luer <ja...@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Primary Exports
+ */
+
+var exports = module.exports = getType;
+
+/*!
+ * Detectable javascript natives
+ */
+
+var natives = {
+    '[object Array]': 'array'
+  , '[object RegExp]': 'regexp'
+  , '[object Function]': 'function'
+  , '[object Arguments]': 'arguments'
+  , '[object Date]': 'date'
+};
+
+/**
+ * ### typeOf (obj)
+ *
+ * Use several different techniques to determine
+ * the type of object being tested.
+ *
+ *
+ * @param {Mixed} object
+ * @return {String} object type
+ * @api public
+ */
+
+function getType (obj) {
+  var str = Object.prototype.toString.call(obj);
+  if (natives[str]) return natives[str];
+  if (obj === null) return 'null';
+  if (obj === undefined) return 'undefined';
+  if (obj === Object(obj)) return 'object';
+  return typeof obj;
+}
+
+exports.Library = Library;
+
+/**
+ * ### Library
+ *
+ * Create a repository for custom type detection.
+ *
+ * ```js
+ * var lib = new type.Library;
+ * ```
+ *
+ */
+
+function Library () {
+  this.tests = {};
+}
+
+/**
+ * #### .of (obj)
+ *
+ * Expose replacement `typeof` detection to the library.
+ *
+ * ```js
+ * if ('string' === lib.of('hello world')) {
+ *   // ...
+ * }
+ * ```
+ *
+ * @param {Mixed} object to test
+ * @return {String} type
+ */
+
+Library.prototype.of = getType;
+
+/**
+ * #### .define (type, test)
+ *
+ * Add a test to for the `.test()` assertion.
+ *
+ * Can be defined as a regular expression:
+ *
+ * ```js
+ * lib.define('int', /^[0-9]+$/);
+ * ```
+ *
+ * ... or as a function:
+ *
+ * ```js
+ * lib.define('bln', function (obj) {
+ *   if ('boolean' === lib.of(obj)) return true;
+ *   var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ];
+ *   if ('string' === lib.of(obj)) obj = obj.toLowerCase();
+ *   return !! ~blns.indexOf(obj);
+ * });
+ * ```
+ *
+ * @param {String} type
+ * @param {RegExp|Function} test
+ * @api public
+ */
+
+Library.prototype.define = function (type, test) {
+  if (arguments.length === 1) return this.tests[type];
+  this.tests[type] = test;
+  return this;
+};
+
+/**
+ * #### .test (obj, test)
+ *
+ * Assert that an object is of type. Will first
+ * check natives, and if that does not pass it will
+ * use the user defined custom tests.
+ *
+ * ```js
+ * assert(lib.test('1', 'int'));
+ * assert(lib.test('yes', 'bln'));
+ * ```
+ *
+ * @param {Mixed} object
+ * @param {String} type
+ * @return {Boolean} result
+ * @api public
+ */
+
+Library.prototype.test = function (obj, type) {
+  if (type === getType(obj)) return true;
+  var test = this.tests[type];
+
+  if (test && 'regexp' === getType(test)) {
+    return test.test(obj);
+  } else if (test && 'function' === getType(test)) {
+    return test(obj);
+  } else {
+    throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
+  }
+};
+
+});
+require.register("chaijs-deep-eql/lib/eql.js", function(exports, require, module){
+/*!
+ * deep-eql
+ * Copyright(c) 2013 Jake Luer <ja...@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependencies
+ */
+
+var type = require('type-detect');
+
+/*!
+ * Buffer.isBuffer browser shim
+ */
+
+var Buffer;
+try { Buffer = require('buffer').Buffer; }
+catch(ex) {
+  Buffer = {};
+  Buffer.isBuffer = function() { return false; }
+}
+
+/*!
+ * Primary Export
+ */
+
+module.exports = deepEqual;
+
+/**
+ * Assert super-strict (egal) equality between
+ * two objects of any type.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @param {Array} memoised (optional)
+ * @return {Boolean} equal match
+ */
+
+function deepEqual(a, b, m) {
+  if (sameValue(a, b)) {
+    return true;
+  } else if ('date' === type(a)) {
+    return dateEqual(a, b);
+  } else if ('regexp' === type(a)) {
+    return regexpEqual(a, b);
+  } else if (Buffer.isBuffer(a)) {
+    return bufferEqual(a, b);
+  } else if ('arguments' === type(a)) {
+    return argumentsEqual(a, b, m);
+  } else if (!typeEqual(a, b)) {
+    return false;
+  } else if (('object' !== type(a) && 'object' !== type(b))
+  && ('array' !== type(a) && 'array' !== type(b))) {
+    return sameValue(a, b);
+  } else {
+    return objectEqual(a, b, m);
+  }
+}
+
+/*!
+ * Strict (egal) equality test. Ensures that NaN always
+ * equals NaN and `-0` does not equal `+0`.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} equal match
+ */
+
+function sameValue(a, b) {
+  if (a === b) return a !== 0 || 1 / a === 1 / b;
+  return a !== a && b !== b;
+}
+
+/*!
+ * Compare the types of two given objects and
+ * return if they are equal. Note that an Array
+ * has a type of `array` (not `object`) and arguments
+ * have a type of `arguments` (not `array`/`object`).
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function typeEqual(a, b) {
+  return type(a) === type(b);
+}
+
+/*!
+ * Compare two Date objects by asserting that
+ * the time values are equal using `saveValue`.
+ *
+ * @param {Date} a
+ * @param {Date} b
+ * @return {Boolean} result
+ */
+
+function dateEqual(a, b) {
+  if ('date' !== type(b)) return false;
+  return sameValue(a.getTime(), b.getTime());
+}
+
+/*!
+ * Compare two regular expressions by converting them
+ * to string and checking for `sameValue`.
+ *
+ * @param {RegExp} a
+ * @param {RegExp} b
+ * @return {Boolean} result
+ */
+
+function regexpEqual(a, b) {
+  if ('regexp' !== type(b)) return false;
+  return sameValue(a.toString(), b.toString());
+}
+
+/*!
+ * Assert deep equality of two `arguments` objects.
+ * Unfortunately, these must be sliced to arrays
+ * prior to test to ensure no bad behavior.
+ *
+ * @param {Arguments} a
+ * @param {Arguments} b
+ * @param {Array} memoize (optional)
+ * @return {Boolean} result
+ */
+
+function argumentsEqual(a, b, m) {
+  if ('arguments' !== type(b)) return false;
+  a = [].slice.call(a);
+  b = [].slice.call(b);
+  return deepEqual(a, b, m);
+}
+
+/*!
+ * Get enumerable properties of a given object.
+ *
+ * @param {Object} a
+ * @return {Array} property names
+ */
+
+function enumerable(a) {
+  var res = [];
+  for (var key in a) res.push(key);
+  return res;
+}
+
+/*!
+ * Simple equality for flat iterable objects
+ * such as Arrays or Node.js buffers.
+ *
+ * @param {Iterable} a
+ * @param {Iterable} b
+ * @return {Boolean} result
+ */
+
+function iterableEqual(a, b) {
+  if (a.length !==  b.length) return false;
+
+  var i = 0;
+  var match = true;
+
+  for (; i < a.length; i++) {
+    if (a[i] !== b[i]) {
+      match = false;
+      break;
+    }
+  }
+
+  return match;
+}
+
+/*!
+ * Extension to `iterableEqual` specifically
+ * for Node.js Buffers.
+ *
+ * @param {Buffer} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function bufferEqual(a, b) {
+  if (!Buffer.isBuffer(b)) return false;
+  return iterableEqual(a, b);
+}
+
+/*!
+ * Block for `objectEqual` ensuring non-existing
+ * values don't get in.
+ *
+ * @param {Mixed} object
+ * @return {Boolean} result
+ */
+
+function isValue(a) {
+  return a !== null && a !== undefined;
+}
+
+/*!
+ * Recursively check the equality of two objects.
+ * Once basic sameness has been established it will
+ * defer to `deepEqual` for each enumerable key
+ * in the object.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function objectEqual(a, b, m) {
+  if (!isValue(a) || !isValue(b)) {
+    return false;
+  }
+
+  if (a.prototype !== b.prototype) {
+    return false;
+  }
+
+  var i;
+  if (m) {
+    for (i = 0; i < m.length; i++) {
+      if ((m[i][0] === a && m[i][1] === b)
+      ||  (m[i][0] === b && m[i][1] === a)) {
+        return true;
+      }
+    }
+  } else {
+    m = [];
+  }
+
+  try {
+    var ka = enumerable(a);
+    var kb = enumerable(b);
+  } catch (ex) {
+    return false;
+  }
+
+  ka.sort();
+  kb.sort();
+
+  if (!iterableEqual(ka, kb)) {
+    return false;
+  }
+
+  m.push([ a, b ]);
+
+  var key;
+  for (i = ka.length - 1; i >= 0; i--) {
+    key = ka[i];
+    if (!deepEqual(a[key], b[key], m)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+});
 require.register("chai/index.js", function(exports, require, module){
 module.exports = require('./lib/chai');
 
@@ -316,7 +725,7 @@ module.exports = require('./lib/chai');
 require.register("chai/lib/chai.js", function(exports, require, module){
 /*!
  * chai
- * Copyright(c) 2011-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -327,7 +736,7 @@ var used = []
  * Chai version
  */
 
-exports.version = '1.7.2';
+exports.version = '1.8.1';
 
 /*!
  * Assertion Error
@@ -400,7 +809,7 @@ require.register("chai/lib/chai/assertion.js", function(exports, require, module
 /*!
  * chai
  * http://chaijs.com
- * Copyright(c) 2011-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -480,6 +889,10 @@ module.exports = function (_chai, util) {
     util.overwriteMethod(this.prototype, name, fn);
   };
 
+  Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) {
+    util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
+  };
+
   /*!
    * ### .assert(expression, message, negateMessage, expected, actual)
    *
@@ -533,7 +946,7 @@ require.register("chai/lib/chai/core/assertions.js", function(exports, require,
 /*!
  * chai
  * http://chaijs.com
- * Copyright(c) 2011-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -545,7 +958,7 @@ module.exports = function (chai, _) {
   /**
    * ### Language Chains
    *
-   * The following are provide as chainable getters to
+   * The following are provided as chainable getters to
    * improve the readability of your assertions. They
    * do not provide an testing capability unless they
    * have been overwritten by a plugin.
@@ -558,6 +971,7 @@ module.exports = function (chai, _) {
    * - is
    * - that
    * - and
+   * - has
    * - have
    * - with
    * - at
@@ -569,7 +983,7 @@ module.exports = function (chai, _) {
    */
 
   [ 'to', 'be', 'been'
-  , 'is', 'and', 'have'
+  , 'is', 'and', 'has', 'have'
   , 'with', 'that', 'at'
   , 'of', 'same' ].forEach(function (chain) {
     Assertion.addProperty(chain, function () {
@@ -677,9 +1091,21 @@ module.exports = function (chai, _) {
 
   function include (val, msg) {
     if (msg) flag(this, 'message', msg);
-    var obj = flag(this, 'object')
+    var obj = flag(this, 'object');
+
+    if (_.type(val) === 'object') {
+      if (!flag(this, 'negate')) {
+        for (var k in val) new Assertion(obj).property(k, val[k]);
+        return;
+      }
+      var subset = {}
+      for (var k in val) subset[k] = obj[k]
+      var expected = _.eql(subset, val);
+    } else {
+      var expected = obj && ~obj.indexOf(val)
+    }
     this.assert(
-        ~obj.indexOf(val)
+        expected
       , 'expected #{this} to include ' + _.inspect(val)
       , 'expected #{this} to not include ' + _.inspect(val));
   }
@@ -776,8 +1202,8 @@ module.exports = function (chai, _) {
    *
    * Asserts that the target is `undefined`.
    *
-   *      expect(undefined).to.be.undefined;
-   *      expect(null).to.not.be.undefined;
+   *     expect(undefined).to.be.undefined;
+   *     expect(null).to.not.be.undefined;
    *
    * @name undefined
    * @api public
@@ -1534,6 +1960,7 @@ module.exports = function (chai, _) {
    * @param {String|RegExp} expected error message
    * @param {String} message _optional_
    * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
+   * @returns error for chaining (null if no error)
    * @api public
    */
 
@@ -1558,7 +1985,10 @@ module.exports = function (chai, _) {
       constructor = null;
       errMsg = null;
     } else if (typeof constructor === 'function') {
-      name = (new constructor()).name;
+      name = constructor.prototype.name || constructor.name;
+      if (name === 'Error' && constructor !== Error) {
+        name = (new constructor()).name;
+      }
     } else {
       constructor = null;
     }
@@ -1572,12 +2002,14 @@ module.exports = function (chai, _) {
             err === desiredError
           , 'expected #{this} to throw #{exp} but #{act} was thrown'
           , 'expected #{this} to not throw #{exp}'
-          , desiredError
-          , err
+          , (desiredError instanceof Error ? desiredError.toString() : desiredError)
+          , (err instanceof Error ? err.toString() : err)
         );
 
+        flag(this, 'object', err);
         return this;
       }
+
       // next, check constructor
       if (constructor) {
         this.assert(
@@ -1585,11 +2017,15 @@ module.exports = function (chai, _) {
           , 'expected #{this} to throw #{exp} but #{act} was thrown'
           , 'expected #{this} to not throw #{exp} but #{act} was thrown'
           , name
-          , err
+          , (err instanceof Error ? err.toString() : err)
         );
 
-        if (!errMsg) return this;
+        if (!errMsg) {
+          flag(this, 'object', err);
+          return this;
+        }
       }
+
       // next, check message
       var message = 'object' === _.type(err) && "message" in err
         ? err.message
@@ -1604,6 +2040,7 @@ module.exports = function (chai, _) {
           , message
         );
 
+        flag(this, 'object', err);
         return this;
       } else if ((message != null) && errMsg && 'string' === typeof errMsg) {
         this.assert(
@@ -1614,6 +2051,7 @@ module.exports = function (chai, _) {
           , message
         );
 
+        flag(this, 'object', err);
         return this;
       } else {
         thrown = true;
@@ -1636,9 +2074,11 @@ module.exports = function (chai, _) {
         thrown === true
       , 'expected #{this} to throw ' + expectedThrown + actuallyGot
       , 'expected #{this} to not throw ' + expectedThrown + actuallyGot
-      , desiredError
-      , thrownError
+      , (desiredError instanceof Error ? desiredError.toString() : desiredError)
+      , (thrownError instanceof Error ? thrownError.toString() : thrownError)
     );
+
+    flag(this, 'object', thrownError);
   };
 
   Assertion.addMethod('throw', assertThrows);
@@ -1657,8 +2097,8 @@ module.exports = function (chai, _) {
    * To check if a constructor will respond to a static function,
    * set the `itself` flag.
    *
-   *    Klass.baz = function(){};
-   *    expect(Klass).itself.to.respondTo('baz');
+   *     Klass.baz = function(){};
+   *     expect(Klass).itself.to.respondTo('baz');
    *
    * @name respondTo
    * @param {String} method
@@ -1686,12 +2126,12 @@ module.exports = function (chai, _) {
    *
    * Sets the `itself` flag, later used by the `respondTo` assertion.
    *
-   *    function Foo() {}
-   *    Foo.bar = function() {}
-   *    Foo.prototype.baz = function() {}
+   *     function Foo() {}
+   *     Foo.bar = function() {}
+   *     Foo.prototype.baz = function() {}
    *
-   *    expect(Foo).itself.to.respondTo('bar');
-   *    expect(Foo).itself.not.to.respondTo('baz');
+   *     expect(Foo).itself.to.respondTo('bar');
+   *     expect(Foo).itself.not.to.respondTo('baz');
    *
    * @name itself
    * @api public
@@ -1805,7 +2245,7 @@ module.exports = function (chai, _) {
 require.register("chai/lib/chai/interface/assert.js", function(exports, require, module){
 /*!
  * chai
- * Copyright(c) 2011-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -1860,13 +2300,12 @@ module.exports = function (chai, util) {
    */
 
   assert.fail = function (actual, expected, message, operator) {
-    throw new chai.AssertionError({
+    message = message || 'assert.fail()';
+    throw new chai.AssertionError(message, {
         actual: actual
       , expected: expected
-      , message: message
       , operator: operator
-      , stackStartFunction: assert.fail
-    });
+    }, assert.fail);
   };
 
   /**
@@ -2462,19 +2901,7 @@ module.exports = function (chai, util) {
    */
 
   assert.include = function (exp, inc, msg) {
-    var obj = new Assertion(exp, msg);
-
-    if (Array.isArray(exp)) {
-      obj.to.include(inc);
-    } else if ('string' === typeof exp) {
-      obj.to.contain.string(inc);
-    } else {
-      throw new chai.AssertionError(
-          'expected an array or string'
-        , null
-        , assert.include
-      );
-    }
+    new Assertion(exp, msg).include(inc);
   };
 
   /**
@@ -2494,19 +2921,7 @@ module.exports = function (chai, util) {
    */
 
   assert.notInclude = function (exp, inc, msg) {
-    var obj = new Assertion(exp, msg);
-
-    if (Array.isArray(exp)) {
-      obj.to.not.include(inc);
-    } else if ('string' === typeof exp) {
-      obj.to.not.contain.string(inc);
-    } else {
-      throw new chai.AssertionError(
-          'expected an array or string'
-        , null
-        , assert.notInclude
-      );
-    }
+    new Assertion(exp, msg).not.include(inc);
   };
 
   /**
@@ -2750,7 +3165,8 @@ module.exports = function (chai, util) {
       errt = null;
     }
 
-    new Assertion(fn, msg).to.Throw(errt, errs);
+    var assertErr = new Assertion(fn, msg).to.Throw(errt, errs);
+    return flag(assertErr, 'object');
   };
 
   /**
@@ -2888,7 +3304,7 @@ module.exports = function (chai, util) {
 require.register("chai/lib/chai/interface/expect.js", function(exports, require, module){
 /*!
  * chai
- * Copyright(c) 2011-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -2903,7 +3319,7 @@ module.exports = function (chai, util) {
 require.register("chai/lib/chai/interface/should.js", function(exports, require, module){
 /*!
  * chai
- * Copyright(c) 2011-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -2982,7 +3398,7 @@ module.exports = function (chai, util) {
 require.register("chai/lib/chai/utils/addChainableMethod.js", function(exports, require, module){
 /*!
  * Chai - addChainingMethod utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3037,15 +3453,27 @@ var call  = Function.prototype.call,
  */
 
 module.exports = function (ctx, name, method, chainingBehavior) {
-  if (typeof chainingBehavior !== 'function')
+  if (typeof chainingBehavior !== 'function') {
     chainingBehavior = function () { };
+  }
+
+  var chainableBehavior = {
+      method: method
+    , chainingBehavior: chainingBehavior
+  };
+
+  // save the methods so we can overwrite them later, if we need to.
+  if (!ctx.__methods) {
+    ctx.__methods = {};
+  }
+  ctx.__methods[name] = chainableBehavior;
 
   Object.defineProperty(ctx, name,
     { get: function () {
-        chainingBehavior.call(this);
+        chainableBehavior.chainingBehavior.call(this);
 
         var assert = function () {
-          var result = method.apply(this, arguments);
+          var result = chainableBehavior.method.apply(this, arguments);
           return result === undefined ? this : result;
         };
 
@@ -3079,7 +3507,7 @@ module.exports = function (ctx, name, method, chainingBehavior) {
 require.register("chai/lib/chai/utils/addMethod.js", function(exports, require, module){
 /*!
  * Chai - addMethod utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3119,7 +3547,7 @@ module.exports = function (ctx, name, method) {
 require.register("chai/lib/chai/utils/addProperty.js", function(exports, require, module){
 /*!
  * Chai - addProperty utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3159,142 +3587,10 @@ module.exports = function (ctx, name, getter) {
 };
 
 });
-require.register("chai/lib/chai/utils/eql.js", function(exports, require, module){
-// This is (almost) directly from Node.js assert
-// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js
-
-module.exports = _deepEqual;
-
-var getEnumerableProperties = require('./getEnumerableProperties');
-
-// for the browser
-var Buffer;
-try {
-  Buffer = require('buffer').Buffer;
-} catch (ex) {
-  Buffer = {
-    isBuffer: function () { return false; }
-  };
-}
-
-function _deepEqual(actual, expected, memos) {
-
-  // 7.1. All identical values are equivalent, as determined by ===.
-  if (actual === expected) {
-    return true;
-
-  } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
-    if (actual.length != expected.length) return false;
-
-    for (var i = 0; i < actual.length; i++) {
-      if (actual[i] !== expected[i]) return false;
-    }
-
-    return true;
-
-  // 7.2. If the expected value is a Date object, the actual value is
-  // equivalent if it is also a Date object that refers to the same time.
-  } else if (expected instanceof Date) {
-    if (!(actual instanceof Date)) return false;
-    return actual.getTime() === expected.getTime();
-
-  // 7.3. Other pairs that do not both pass typeof value == 'object',
-  // equivalence is determined by ==.
-  } else if (typeof actual != 'object' && typeof expected != 'object') {
-    return actual === expected;
-
-  } else if (expected instanceof RegExp) {
-    if (!(actual instanceof RegExp)) return false;
-    return actual.toString() === expected.toString();
-
-  // 7.4. For all other Object pairs, including Array objects, equivalence is
-  // determined by having the same number of owned properties (as verified
-  // with Object.prototype.hasOwnProperty.call), the same set of keys
-  // (although not necessarily the same order), equivalent values for every
-  // corresponding key, and an identical 'prototype' property. Note: this
-  // accounts for both named and indexed properties on Arrays.
-  } else {
-    return objEquiv(actual, expected, memos);
-  }
-}
-
-function isUndefinedOrNull(value) {
-  return value === null || value === undefined;
-}
-
-function isArguments(object) {
-  return Object.prototype.toString.call(object) == '[object Arguments]';
-}
-
-function objEquiv(a, b, memos) {
-  if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
-    return false;
-
-  // an identical 'prototype' property.
-  if (a.prototype !== b.prototype) return false;
-
-  // check if we have already compared a and b
-  var i;
-  if (memos) {
-    for(i = 0; i < memos.length; i++) {
-      if ((memos[i][0] === a && memos[i][1] === b) ||
-          (memos[i][0] === b && memos[i][1] === a))
-        return true;
-    }
-  } else {
-    memos = [];
-  }
-
-  //~~~I've managed to break Object.keys through screwy arguments passing.
-  //   Converting to array solves the problem.
-  if (isArguments(a)) {
-    if (!isArguments(b)) {
-      return false;
-    }
-    a = pSlice.call(a);
-    b = pSlice.call(b);
-    return _deepEqual(a, b, memos);
-  }
-  try {
-    var ka = getEnumerableProperties(a),
-        kb = getEnumerableProperties(b),
-        key;
-  } catch (e) {//happens when one is a string literal and the other isn't
-    return false;
-  }
-
-  // having the same number of owned properties (keys incorporates
-  // hasOwnProperty)
-  if (ka.length != kb.length)
-    return false;
-
-  //the same set of keys (although not necessarily the same order),
-  ka.sort();
-  kb.sort();
-  //~~~cheap key test
-  for (i = ka.length - 1; i >= 0; i--) {
-    if (ka[i] != kb[i])
-      return false;
-  }
-
-  // remember objects we have compared to guard against circular references
-  memos.push([ a, b ]);
-
-  //equivalent values for every corresponding key, and
-  //~~~possibly expensive deep test
-  for (i = ka.length - 1; i >= 0; i--) {
-    key = ka[i];
-    if (!_deepEqual(a[key], b[key], memos)) return false;
-  }
-
-  return true;
-}
-
-});
 require.register("chai/lib/chai/utils/flag.js", function(exports, require, module){
 /*!
  * Chai - flag utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3329,7 +3625,7 @@ module.exports = function (obj, key, value) {
 require.register("chai/lib/chai/utils/getActual.js", function(exports, require, module){
 /*!
  * Chai - getActual utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3351,7 +3647,7 @@ module.exports = function (obj, args) {
 require.register("chai/lib/chai/utils/getEnumerableProperties.js", function(exports, require, module){
 /*!
  * Chai - getEnumerableProperties utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3379,7 +3675,7 @@ module.exports = function getEnumerableProperties(object) {
 require.register("chai/lib/chai/utils/getMessage.js", function(exports, require, module){
 /*!
  * Chai - message composition utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3431,7 +3727,7 @@ module.exports = function (obj, args) {
 require.register("chai/lib/chai/utils/getName.js", function(exports, require, module){
 /*!
  * Chai - getName utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3454,7 +3750,7 @@ module.exports = function (func) {
 require.register("chai/lib/chai/utils/getPathValue.js", function(exports, require, module){
 /*!
  * Chai - getPathValue utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * @see https://github.com/logicalparadox/filtr
  * MIT Licensed
  */
@@ -3559,7 +3855,7 @@ function _getPathValue (parsed, obj) {
 require.register("chai/lib/chai/utils/getProperties.js", function(exports, require, module){
 /*!
  * Chai - getProperties utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -3659,7 +3955,7 @@ exports.transferFlags = require('./transferFlags');
  * Deep equal utility
  */
 
-exports.eql = require('./eql');
+exports.eql = require('deep-eql');
 
 /*!
  * Deep path value
@@ -3703,6 +3999,12 @@ exports.overwriteMethod = require('./overwriteMethod');
 
 exports.addChainableMethod = require('./addChainableMethod');
 
+/*!
+ * Overwrite chainable method
+ */
+
+exports.overwriteChainableMethod = require('./overwriteChainableMethod');
+
 
 });
 require.register("chai/lib/chai/utils/inspect.js", function(exports, require, module){
@@ -4031,7 +4333,7 @@ function objectToString(o) {
 require.register("chai/lib/chai/utils/objDisplay.js", function(exports, require, module){
 /*!
  * Chai - flag utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -4082,7 +4384,7 @@ module.exports = function (obj) {
 require.register("chai/lib/chai/utils/overwriteMethod.js", function(exports, require, module){
 /*!
  * Chai - overwriteMethod utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -4136,7 +4438,7 @@ module.exports = function (ctx, name, method) {
 require.register("chai/lib/chai/utils/overwriteProperty.js", function(exports, require, module){
 /*!
  * Chai - overwriteProperty utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -4190,10 +4492,66 @@ module.exports = function (ctx, name, getter) {
 };
 
 });
+require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function(exports, require, module){
+/*!
+ * Chai - overwriteChainableMethod utility
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### overwriteChainableMethod (ctx, name, fn)
+ *
+ * Overwites an already existing chainable method
+ * and provides access to the previous function or
+ * property.  Must return functions to be used for
+ * name.
+ *
+ *     utils.overwriteChainableMethod(chai.Assertion.prototype, 'length',
+ *       function (_super) {
+ *       }
+ *     , function (_super) {
+ *       }
+ *     );
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ *     chai.Assertion.overwriteChainableMethod('foo', fn, fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ *     expect(myFoo).to.have.length(3);
+ *     expect(myFoo).to.have.length.above(3);
+ *
+ * @param {Object} ctx object whose method / property is to be overwritten
+ * @param {String} name of method / property to overwrite
+ * @param {Function} method function that returns a function to be used for name
+ * @param {Function} chainingBehavior function that returns a function to be used for property
+ * @name overwriteChainableMethod
+ * @api public
+ */
+
+module.exports = function (ctx, name, method, chainingBehavior) {
+  var chainableBehavior = ctx.__methods[name];
+
+  var _chainingBehavior = chainableBehavior.chainingBehavior;
+  chainableBehavior.chainingBehavior = function () {
+    var result = chainingBehavior(_chainingBehavior).call(this);
+    return result === undefined ? this : result;
+  };
+
+  var _method = chainableBehavior.method;
+  chainableBehavior.method = function () {
+    var result = method(_method).apply(this, arguments);
+    return result === undefined ? this : result;
+  };
+};
+
+});
 require.register("chai/lib/chai/utils/test.js", function(exports, require, module){
 /*!
  * Chai - test utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -4222,7 +4580,7 @@ module.exports = function (obj, args) {
 require.register("chai/lib/chai/utils/transferFlags.js", function(exports, require, module){
 /*!
  * Chai - transferFlags utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -4269,7 +4627,7 @@ module.exports = function (assertion, object, includeAll) {
 require.register("chai/lib/chai/utils/type.js", function(exports, require, module){
 /*!
  * Chai - type utility
- * Copyright(c) 2012-2013 Jake Luer <ja...@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <ja...@alogicalparadox.com>
  * MIT Licensed
  */
 
@@ -4314,17 +4672,25 @@ module.exports = function (obj) {
 };
 
 });
+
+
+
+
 require.alias("chaijs-assertion-error/index.js", "chai/deps/assertion-error/index.js");
 require.alias("chaijs-assertion-error/index.js", "chai/deps/assertion-error/index.js");
 require.alias("chaijs-assertion-error/index.js", "assertion-error/index.js");
 require.alias("chaijs-assertion-error/index.js", "chaijs-assertion-error/index.js");
-
-require.alias("chai/index.js", "chai/index.js");
-
-if (typeof exports == "object") {
+require.alias("chaijs-deep-eql/lib/eql.js", "chai/deps/deep-eql/lib/eql.js");
+require.alias("chaijs-deep-eql/lib/eql.js", "chai/deps/deep-eql/index.js");
+require.alias("chaijs-deep-eql/lib/eql.js", "deep-eql/index.js");
+require.alias("chaijs-type-detect/lib/type.js", "chaijs-deep-eql/deps/type-detect/lib/type.js");
+require.alias("chaijs-type-detect/lib/type.js", "chaijs-deep-eql/deps/type-detect/index.js");
+require.alias("chaijs-type-detect/lib/type.js", "chaijs-type-detect/index.js");
+require.alias("chaijs-deep-eql/lib/eql.js", "chaijs-deep-eql/index.js");
+require.alias("chai/index.js", "chai/index.js");if (typeof exports == "object") {
   module.exports = require("chai");
 } else if (typeof define == "function" && define.amd) {
-  define(function(){ return require("chai"); });
+  define([], function(){ return require("chai"); });
 } else {
   this["chai"] = require("chai");
 }})();

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4e60f0b7/src/fauxton/test/test.config.underscore
----------------------------------------------------------------------
diff --git a/src/fauxton/test/test.config.underscore b/src/fauxton/test/test.config.underscore
index 5cebe78..95494a4 100644
--- a/src/fauxton/test/test.config.underscore
+++ b/src/fauxton/test/test.config.underscore
@@ -7,7 +7,11 @@ require.config(
 require([
         "app",
         <% _.each(testFiles, function (test) {%>
+           <% if (test[0] === '.') { %>
            '../<%= test %>',
+           <% } else { %>
+           '<%= test %>',
+           <% }  %>
         <% }) %>
 ], function() {
   if (window.mochaPhantomJS) { mochaPhantomJS.run(); }


[2/2] couchdb commit: updated refs/heads/master to 4e60f0b

Posted by ga...@apache.org.
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/master
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');