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 2015/03/26 11:49:53 UTC

[3/3] fauxton commit: updated refs/heads/master to 96d31bc

Create Index Results

This converts the AllDocsList backbone view to React.


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

Branch: refs/heads/master
Commit: 96d31bc7d0e7101c5a364fbaab2524d1e626e3d5
Parents: 2bc72f2
Author: Garren Smith <ga...@gmail.com>
Authored: Thu Mar 5 15:58:18 2015 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Thu Mar 26 12:39:57 2015 +0200

----------------------------------------------------------------------
 .travis.yml                                     |   2 +-
 .../components/react-components.react.jsx       |  17 +-
 app/addons/components/tests/docSpec.react.jsx   |   8 +-
 .../tests/nightwatch/checkDatabaseTooltip.js    |   2 +-
 app/addons/documents/header/header.actions.js   |  26 +-
 .../documents/header/header.actiontypes.js      |   6 +-
 app/addons/documents/header/header.react.jsx    |  62 +--
 app/addons/documents/header/header.stores.js    |  75 +---
 app/addons/documents/index-editor/actions.js    |   7 +-
 app/addons/documents/index-results/actions.js   | 151 ++++++++
 .../documents/index-results/actiontypes.js      |  21 ++
 .../index-results.components.react.jsx          | 120 +++++-
 app/addons/documents/index-results/stores.js    | 270 +++++++++++++
 .../tests/index-results.actionsSpec.js          | 232 ++++++++++++
 .../index-results.componentsSpec.react.jsx      |  31 ++
 .../tests/index-results.storesSpec.js           | 372 ++++++++++++++++++
 app/addons/documents/pagination/actions.js      |  32 +-
 app/addons/documents/pagination/actiontypes.js  |   2 -
 app/addons/documents/pagination/stores.js       |  22 +-
 .../pagination/tests/pagination.actionsSpec.js  |  75 ++--
 .../pagination/tests/paginationStoreSpec.js     |  68 +++-
 app/addons/documents/resources.js               |  16 +-
 app/addons/documents/routes-documents.js        | 375 +++++++++----------
 app/addons/documents/routes-index-editor.js     |  35 +-
 app/addons/documents/shared-routes.js           |  71 ----
 app/addons/documents/tests/actionsSpec.js       |  14 +-
 app/addons/documents/tests/headerSpec.react.jsx |  39 +-
 .../tests/nightwatch/deletesDocument.js         |   3 +-
 .../documents/tests/nightwatch/paginateView.js  |  14 +-
 app/addons/documents/tests/resourcesSpec.js     | 136 ++++---
 app/addons/documents/tests/routeSpec.js         |  11 -
 app/addons/documents/tests/viewsSpec.js         |  29 --
 app/addons/documents/views.js                   | 260 +------------
 assets/less/animations.less                     |   1 -
 assets/less/react-animations.less               |  18 +
 test/mocha/testUtils.js                         |   9 +-
 36 files changed, 1781 insertions(+), 851 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 21e7bbe..0bab741 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,7 +18,7 @@ before_script:
   - npm install -g grunt-cli
   - grunt test
   - grunt dev &
-  - sleep 5
+  - sleep 10
 script:
   - grunt nightwatch
 notifications:

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/components/react-components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx
index b5727f9..a21c794 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -191,7 +191,13 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) {
   var Document = React.createClass({
 
     propTypes: {
-      docIdentifier: React.PropTypes.string.isRequired
+      docIdentifier: React.PropTypes.string.isRequired,
+      docChecked: React.PropTypes.func.isRequired
+    },
+
+    onChange: function (e) {
+      e.preventDefault();
+      this.props.docChecked(this.props.docIdentifier, this.props.doc, e);
     },
 
     getUrlFragment: function () {
@@ -213,19 +219,22 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) {
             id={'checkbox-' + this.props.docIdentifier}
             checked={this.props.checked ? 'checked="checked"': null}
             type="checkbox"
-            onChange={this.props.onChange}
+            onChange={this.onChange}
             className="js-row-select" />
-          <label
+          <label onClick={this.onChange}
             className="label-checkbox-doclist"
             htmlFor={'checkbox-' + this.props.docIdentifier} />
         </div>
       );
     },
 
+    onDoubleClick: function (e) {
+      this.props.onDoubleClick(this.props.docIdentifier, this.props.doc, e);
+    },
 
     render: function () {
       return (
-        <div onDoubleClick={this.props.onDoubleClick} className="doc-row">
+        <div data-id={this.props.docIdentifier} onDoubleClick={this.onDoubleClick} className="doc-row">
           <div className="custom-inputs">
             {this.getCheckbox()}
           </div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/components/tests/docSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/tests/docSpec.react.jsx b/app/addons/components/tests/docSpec.react.jsx
index ce3a94c..119abe6 100644
--- a/app/addons/components/tests/docSpec.react.jsx
+++ b/app/addons/components/tests/docSpec.react.jsx
@@ -54,7 +54,7 @@ define([
         <ReactComponents.Document checked={true} docIdentifier="foo" />,
         container
       );
-      assert.equal($(el.getDOMNode()).find('#checkbox-foo').attr('checked'), 'checked');
+      assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), 'checked');
     });
 
     it('you can uncheck it', function () {
@@ -62,17 +62,17 @@ define([
         <ReactComponents.Document docIdentifier="foo" />,
         container
       );
-      assert.equal($(el.getDOMNode()).find('#checkbox-foo').attr('checked'), undefined);
+      assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), undefined);
     });
 
     it('it calls an onchange callback', function () {
       var spy = sinon.spy();
 
       el = TestUtils.renderIntoDocument(
-        <ReactComponents.Document onChange={spy} docIdentifier="foo" />,
+        <ReactComponents.Document docChecked={spy} docIdentifier="foo" />,
         container
       );
-      var testEl = $(el.getDOMNode()).find('#checkbox-foo')[0];
+      var testEl = $(el.getDOMNode()).find('input[type="checkbox"]')[0];
       React.addons.TestUtils.Simulate.change(testEl, {target: {value: 'Hello, world'}});
       assert.ok(spy.calledOnce);
     });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
index 3808338..dc25f6a 100644
--- a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
+++ b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
@@ -34,7 +34,7 @@ module.exports = {
       .waitForElementPresent('.control-select-all', waitTime, false)
       .click('.control-delete')
       .acceptAlert()
-      .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+      .waitForElementVisible('.alert.alert-info', waitTime, false)
       .click('#nav-links a[href="#/_all_dbs"]')
 
       // now let's look at the actual UI to confirm the tooltip appears

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.actions.js b/app/addons/documents/header/header.actions.js
index 7be2a05..5a6dfe0 100644
--- a/app/addons/documents/header/header.actions.js
+++ b/app/addons/documents/header/header.actions.js
@@ -18,31 +18,37 @@ define([
 function (app, FauxtonAPI, ActionTypes) {
 
   return {
-    toggleCollapseDocuments: function () {
+    collapseDocuments: function () {
       FauxtonAPI.dispatch({
         type: ActionTypes.COLLAPSE_DOCUMENTS
       });
 
-      FauxtonAPI.Events.trigger('headerbar:collapse');
     },
 
-    toggleSelectAllDocuments: function (on) {
-      FauxtonAPI.Events.trigger('headerbar:selectall', on);
+    unCollapseDocuments: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.EXPAND_DOCUMENTS
+      });
+
     },
 
-    updateDocumentCount: function (options) {
+    selectAllDocuments: function () {
       FauxtonAPI.dispatch({
-        type: ActionTypes.UPDATE_DOCUMENT_COUNT,
-        options: options
+        type: ActionTypes.SELECT_ALL_DOCUMENTS
       });
     },
 
-    deleteSelected: function () {
+    deSelectAllDocuments: function () {
       FauxtonAPI.dispatch({
-        type: ActionTypes.DELETE_SELECTED
+        type: ActionTypes.DESELECT_ALL_DOCUMENTS
       });
+    },
 
-      FauxtonAPI.Events.trigger('headerbar:deleteselected');
+    updateDocumentCount: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.UPDATE_DOCUMENT_COUNT,
+        options: options
+      });
     },
 
     toggleHeaderControls: function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.actiontypes.js b/app/addons/documents/header/header.actiontypes.js
index 180dabb..18cc41f 100644
--- a/app/addons/documents/header/header.actiontypes.js
+++ b/app/addons/documents/header/header.actiontypes.js
@@ -14,9 +14,11 @@ define([], function () {
   return {
     UPDATE_DOCUMENT_COUNT: 'UPDATE_DOCUMENT_COUNT',
     COLLAPSE_DOCUMENTS: 'COLLAPSE_DOCUMENTS',
+    EXPAND_DOCUMENTS: 'UNCOLLAPSE_DOCUMENTS',
     TOGGLE_HEADER_CONTROLS: 'TOGGLE_HEADER_CONTROLS',
     RESET_HEADER_BAR: 'RESET_HEADER_BAR',
-    DELETE_SELECTED_DOCUMENTS: 'DELETE_SELECTED_DOCUMENTS'
+    DELETE_SELECTED_DOCUMENTS: 'DELETE_SELECTED_DOCUMENTS',
+    SELECT_ALL_DOCUMENTS: 'SELECT_ALL_DOCUMENTS',
+    DESELECT_ALL_DOCUMENTS: 'DESELECT_ALL_DOCUMENTS'
   };
 });
-

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.react.jsx b/app/addons/documents/header/header.react.jsx
index 5906974..a5a1f5d 100644
--- a/app/addons/documents/header/header.react.jsx
+++ b/app/addons/documents/header/header.react.jsx
@@ -17,20 +17,24 @@ define([
   'addons/documents/header/header.stores',
   'addons/documents/header/header.actions',
   'addons/components/react-components.react',
+  'addons/documents/index-results/stores',
+  'addons/documents/index-results/actions',
 ],
 
-function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
+function (app, FauxtonAPI, React, Stores, Actions, ReactComponents, IndexResultsStore, IndexResultsActions) {
   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
   var headerBarStore = Stores.headerBarStore;
   var bulkDocumentHeaderStore = Stores.bulkDocumentHeaderStore;
+  var indexResultsStore = IndexResultsStore.indexResultsStore;
   var ToggleHeaderButton = ReactComponents.ToggleHeaderButton;
 
   var BulkDocumentHeaderController = React.createClass({
     getStoreState: function () {
       return {
-        areDocumentsCollapsed: bulkDocumentHeaderStore.getCollapsedState(),
-        isDeselectPossible: bulkDocumentHeaderStore.getIsDeselectPossible(),
-        isSelectAllPossible: bulkDocumentHeaderStore.getIsSelectAllPossible()
+        canCollapseDocs: indexResultsStore.canCollapseDocs(),
+        canUncollapseDocs: indexResultsStore.canUncollapseDocs(),
+        canDeselectAll: indexResultsStore.canDeselectAll(),
+        canSelectAll: indexResultsStore.canSelectAll()
       };
     },
 
@@ -39,11 +43,11 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
     },
 
     componentDidMount: function () {
-      bulkDocumentHeaderStore.on('change', this.onChange, this);
+      indexResultsStore.on('change', this.onChange, this);
     },
 
     componentWillUnmount: function () {
-      bulkDocumentHeaderStore.off('change', this.onChange);
+      indexResultsStore.off('change', this.onChange);
     },
 
     onChange: function () {
@@ -52,9 +56,10 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
 
     render: function () {
       var baseClass = 'header-control-box header-control-square ',
-          isDeselectPossible = this.state.isDeselectPossible,
-          isSelectAllPossible = this.state.isSelectAllPossible,
-          areDocumentsCollapsed = this.state.areDocumentsCollapsed;
+          canDeselectAll = this.state.canDeselectAll,
+          canSelectAll = this.state.canSelectAll,
+          canCollapseDocs = this.state.canCollapseDocs,
+          canUncollapseDocs = this.state.canUncollapseDocs;
 
       return (
         <div className='alternative-header'>
@@ -64,8 +69,8 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
             innerClasses={''}
             containerClasses={baseClass + 'control-select-all'}
             text={''}
-            setEnabledClass={!isSelectAllPossible}
-            disabled={!isSelectAllPossible}
+            setEnabledClass={!canSelectAll}
+            disabled={!canSelectAll}
             title={'Select all Documents'} />
 
           <ToggleHeaderButton
@@ -74,29 +79,30 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
             innerClasses={''}
             containerClasses={baseClass + 'control-de-select-all'}
             text={''}
-            setEnabledClass={!isDeselectPossible}
-            disabled={!isDeselectPossible}
+            setEnabledClass={!canDeselectAll}
+            disabled={!canDeselectAll}
             title={'Deselect all Documents'} />
 
+
           <ToggleHeaderButton
             fonticon={'fonticon-collapse'}
-            toggleCallback={this.toggleCollapseDocuments}
+            toggleCallback={this.collapseDocuments}
             innerClasses={''}
             containerClasses={baseClass + 'control-collapse'}
             text={''}
-            setEnabledClass={areDocumentsCollapsed}
-            disabled={areDocumentsCollapsed}
-            title={'Collapse all'} />
+            setEnabledClass={!canCollapseDocs}
+            disabled={!canCollapseDocs}
+            title={'Collapse Selected'} />
 
           <ToggleHeaderButton
             fonticon={'fonticon-expand'}
-            toggleCallback={this.toggleCollapseDocuments}
+            toggleCallback={this.unCollapseDocuments}
             innerClasses={''}
             containerClasses={baseClass + 'control-expand'}
             text={''}
-            setEnabledClass={!areDocumentsCollapsed}
-            disabled={!areDocumentsCollapsed}
-            title={'Expand all'} />
+            setEnabledClass={!canUncollapseDocs}
+            disabled={!canUncollapseDocs}
+            title={'Expand Selected'} />
 
           <ToggleHeaderButton
             fonticon={'fonticon-trash'}
@@ -117,16 +123,20 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
       );
     },
 
-    toggleCollapseDocuments: function () {
-      Actions.toggleCollapseDocuments();
+    collapseDocuments: function () {
+      Actions.collapseDocuments();
+    },
+
+    unCollapseDocuments: function () {
+      Actions.unCollapseDocuments();
     },
 
     selectAllDocuments: function () {
-      Actions.toggleSelectAllDocuments(false);
+      Actions.selectAllDocuments();
     },
 
     deSelectAllDocuments: function () {
-      Actions.toggleSelectAllDocuments(true);
+      Actions.deSelectAllDocuments();
     },
 
     cancelView: function () {
@@ -134,7 +144,7 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
     },
 
     deleteSelected: function () {
-      Actions.deleteSelected();
+      IndexResultsActions.deleteSelected();
     }
   });
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.stores.js b/app/addons/documents/header/header.stores.js
index 4085b0e..bc57a34 100644
--- a/app/addons/documents/header/header.stores.js
+++ b/app/addons/documents/header/header.stores.js
@@ -12,79 +12,13 @@
 
 define([
   'api',
-  'addons/documents/header/header.actiontypes'
+  'addons/documents/header/header.actiontypes',
+  'addons/documents/index-results/actiontypes'
 ],
 
-function (FauxtonAPI, ActionTypes) {
+function (FauxtonAPI, ActionTypes, IndexResultsActions) {
   var Stores = {};
 
-  Stores.BulkDocumentHeaderStore = FauxtonAPI.Store.extend({
-    initialize: function () {
-      this.reset();
-    },
-
-    reset: function () {
-      this._collapsedDocuments = false;
-      this._selectedDocumentsCount = 0;
-      this._documentsOnPageCount = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE;
-    },
-
-    toggleCollapse: function () {
-      this._collapsedDocuments = !this._collapsedDocuments;
-    },
-
-    getCollapsedState: function () {
-      return this._collapsedDocuments;
-    },
-
-    getSelectedAllState: function () {
-      return this._selectedAllDocuments;
-    },
-
-    getIsDeselectPossible: function () {
-      if (this._selectedDocumentsonPageCount > 0) {
-        return true;
-      }
-      return false;
-    },
-
-    getIsSelectAllPossible: function () {
-      if (this._selectedDocumentsonPageCount < this._documentsOnPageCount) {
-        return true;
-      }
-      return false;
-    },
-
-    setSelectedDocumentCount: function (options) {
-      this._selectedDocumentsonPageCount = options.selectedOnPage;
-      this._documentsOnPageCount = options.documentsOnPageCount;
-    },
-
-    dispatch: function (action) {
-      switch (action.type) {
-
-        case ActionTypes.COLLAPSE_DOCUMENTS:
-          this.toggleCollapse();
-          this.triggerChange();
-        break;
-
-        case ActionTypes.UPDATE_DOCUMENT_COUNT:
-          this.setSelectedDocumentCount(action.options);
-          this.triggerChange();
-        break;
-
-        case ActionTypes.RESET_HEADER_BAR:
-          this.reset();
-          this.triggerChange();
-        break;
-
-        default:
-        return;
-      }
-    }
-
-  });
-
   Stores.HeaderBarStore = FauxtonAPI.Store.extend({
     initialize: function (options) {
       this.reset();
@@ -137,8 +71,5 @@ function (FauxtonAPI, ActionTypes) {
   Stores.headerBarStore = new Stores.HeaderBarStore();
   Stores.headerBarStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.headerBarStore.dispatch);
 
-  Stores.bulkDocumentHeaderStore = new Stores.BulkDocumentHeaderStore();
-  Stores.bulkDocumentHeaderStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.bulkDocumentHeaderStore.dispatch);
-
   return Stores;
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-editor/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js
index 263849b..84bf535 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -14,9 +14,10 @@ define([
   'app',
   'api',
   'addons/documents/resources',
-  'addons/documents/index-editor/actiontypes'
+  'addons/documents/index-editor/actiontypes',
+  'addons/documents/index-results/actions'
 ],
-function (app, FauxtonAPI, Documents, ActionTypes) {
+function (app, FauxtonAPI, Documents, ActionTypes, IndexResultsActions) {
   var ActionHelpers = {
     createNewDesignDoc: function (id, database) {
       var designDoc = {
@@ -125,7 +126,7 @@ function (app, FauxtonAPI, Documents, ActionTypes) {
             FauxtonAPI.navigate(fragment, {trigger: true});
           }
 
-          FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: designDoc.id, view: viewInfo.viewName});
+          IndexResultsActions.reloadResultsList();
         });
       }
     },

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js
new file mode 100644
index 0000000..3e882eb
--- /dev/null
+++ b/app/addons/documents/index-results/actions.js
@@ -0,0 +1,151 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'app',
+  'api',
+  'addons/documents/index-results/actiontypes',
+  'addons/documents/index-results/stores',
+  'addons/documents/header/header.stores',
+  'addons/documents/header/header.actions',
+  'addons/documents/resources'
+],
+function (app, FauxtonAPI, ActionTypes, Stores, HeaderStores, HeaderActions, Documents) {
+  var indexResultsStore = Stores.indexResultsStore;
+  var headerBarStore = HeaderStores.headerBarStore;
+
+  var errorMessage = function (ids) {
+    var msg = 'Failed to delete your document!';
+
+    if (ids) {
+      msg = 'Failed to delete: ' + ids.join(', ');
+    }
+
+    FauxtonAPI.addNotification({
+      msg: msg,
+      type: 'error',
+      clear:  true
+    });
+  };
+
+  return {
+    newResultsList: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.INDEX_RESULTS_NEW_RESULTS,
+        options: options
+      });
+
+      if (!options.collection.fetch) { return; }
+
+      return options.collection.fetch({reset: true}).then(function () {
+        this.resultsListReset();
+      }.bind(this), function (xhr) {
+        // TODO: handle error requests that slip through
+        // This should just throw a notification, not break the page
+        var errorMsg = 'Bad Request';
+
+        try {
+          var responseText = JSON.parse(xhr.responseText);
+          if (responseText.reason) {
+            errorMsg = responseText.reason;
+          }
+        } catch (e) {
+          console.log(e);
+        }
+
+        FauxtonAPI.addNotification({
+          msg: errorMsg,
+          type: "error",
+          clear:  true
+       });
+      });
+    },
+
+    reloadResultsList: function () {
+      return this.newResultsList({
+        collection: indexResultsStore.getCollection(),
+        deleteable: indexResultsStore.isDeleteable()
+      });
+    },
+
+    resultsListReset: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.INDEX_RESULTS_RESET
+      });
+    },
+
+    selectDoc: function (id) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.INDEX_RESULTS_SELECT_DOC,
+        id: id
+      });
+
+      //show menu
+      if (!headerBarStore.getToggleStatus()) {
+        HeaderActions.toggleHeaderControls();
+        return;
+      }
+
+      //hide menu
+      if (headerBarStore.getToggleStatus() && indexResultsStore.getSelectedItemsLength() === 0) {
+        HeaderActions.toggleHeaderControls();
+      }
+    },
+
+    selectListOfDocs: function (ids) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.INDEX_RESULTS_SELECT_LIST_OF_DOCS,
+        ids: ids
+      });
+    },
+
+    clearResults: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.INDEX_RESULTS_CLEAR_RESULTS
+      });
+    },
+
+    deleteSelected: function () {
+      var itemsLength = indexResultsStore.getSelectedItemsLength();
+      var msg = "Are you sure you want to delete these " + itemsLength + " docs?";
+
+      if (itemsLength === 0 || !window.confirm(msg)) {
+        return false;
+      }
+
+      var reloadResultsList = _.bind(this.reloadResultsList, this);
+      var selectListOfDocs = _.bind(this.selectListOfDocs, this);
+      var selectedIds = [];
+
+      indexResultsStore.createBulkDeleteFromSelected().bulkDelete()
+      .then(function (ids) {
+        FauxtonAPI.addNotification({
+          msg: 'Successfully deleted your docs',
+          clear:  true
+        });
+
+        if (!_.isEmpty(ids.errorIds)) {
+          errorMessage(ids.errorIds);
+          selectedIds = ids.errorIds;
+        }
+      }, function (ids) {
+        errorMessage(ids);
+        selectedIds = ids;
+      })
+      .always(function () {
+        reloadResultsList().then(function () {
+          selectListOfDocs(selectedIds);
+        });
+      });
+    }
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/actiontypes.js b/app/addons/documents/index-results/actiontypes.js
new file mode 100644
index 0000000..72157c7
--- /dev/null
+++ b/app/addons/documents/index-results/actiontypes.js
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([], function () {
+  return {
+    INDEX_RESULTS_NEW_RESULTS: 'INDEX_RESULTS_NEW_RESULTS',
+    INDEX_RESULTS_RESET: 'INDEX_RESULTS_RESET',
+    INDEX_RESULTS_SELECT_DOC: 'INDEX_RESULTS_SELECT_DOC',
+    INDEX_RESULTS_SELECT_LIST_OF_DOCS: 'INDEX_RESULTS_SELECT_LIST_OF_DOCS',
+    INDEX_RESULTS_CLEAR_RESULTS: 'INDEX_RESULTS_CLEAR_RESULTS'
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/index-results.components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/index-results.components.react.jsx b/app/addons/documents/index-results/index-results.components.react.jsx
index 8c77739..92fc018 100644
--- a/app/addons/documents/index-results/index-results.components.react.jsx
+++ b/app/addons/documents/index-results/index-results.components.react.jsx
@@ -13,10 +13,18 @@
 define([
   'app',
   'api',
-  'react'
+  'react',
+  'addons/documents/index-results/stores',
+  'addons/documents/index-results/actions',
+  'addons/components/react-components.react',
+  'addons/documents/resources',
+
+  "plugins/prettify"
 ],
 
-function (app, FauxtonAPI, React) {
+function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
+  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+  var store = Stores.indexResultsStore;
 
   var NoResultScreen = React.createClass({
     render: function () {
@@ -28,10 +36,118 @@ function (app, FauxtonAPI, React) {
     }
   });
 
+  var ResultsScreen = React.createClass({
+
+    onDoubleClick: function (id, doc) {
+      FauxtonAPI.navigate(doc.url);
+    },
+
+    getUrlFragment: function (url) {
+      if (this.props.hasReduce) {
+        return null;
+      }
+
+
+      return (
+        <a href={url}>
+          <i className="fonticon-pencil"></i>
+        </a>);
+    },
+
+    getDocumentList: function () {
+      return _.map(this.props.results, function (doc) {
+        return (
+         <Components.Document
+           key={doc.id}
+           doc={doc}
+           onDoubleClick={this.onDoubleClick}
+           keylabel={doc.keylabel}
+           docContent={doc.content}
+           checked={this.props.isSelected(doc.id)}
+           docChecked={this.props.docChecked}
+           docIdentifier={doc.id} >
+           {this.getUrlFragment('#' + doc.url)}
+         </Components.Document>
+       );
+      }, this);
+    },
+
+    render: function () {
+      var classNames = 'view';
+
+      if (this.props.isDeleteable) {
+        classNames += ' show-select';
+      }
+
+      return (
+      <div className={classNames}>
+        <div id="doc-list">
+          <ReactCSSTransitionGroup transitionName={'slow-fade'}>
+            {this.getDocumentList()}
+          </ReactCSSTransitionGroup>
+        </div>
+      </div>
+      );
+    },
+
+    componentDidMount: function () {
+      prettyPrint();
+    },
+
+    componentDidUpdate: function () {
+      prettyPrint();
+    },
+
+  });
+
   var ViewResultListController = React.createClass({
+    getStoreState: function () {
+      return {
+        hasResults: store.hasResults(),
+        results: store.getResults(),
+        isDeleteable: store.isDeleteable(),
+        isSelected: store.isSelected,
+        hasReduce: store.hasReduce()
+      };
+    },
+
+    isSelected: function (id) {
+      return this.state.isSelected(id);
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    componentDidMount: function () {
+      store.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      store.off('change', this.onChange);
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    docChecked: function (id) {
+      Actions.selectDoc(id);
+    },
+
     render: function () {
       var view = <NoResultScreen />;
 
+      if (this.state.hasResults) {
+        view = <ResultsScreen
+          isCollapsed={this.isCollapsed}
+          isSelected={this.isSelected}
+          hasReduce={this.state.hasReduce}
+          isDeleteable={this.state.isDeleteable}
+          docChecked={this.docChecked}
+          results={this.state.results} />;
+      }
+
       return (
         view
       );

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js
new file mode 100644
index 0000000..1f2382d
--- /dev/null
+++ b/app/addons/documents/index-results/stores.js
@@ -0,0 +1,270 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/documents/index-results/actiontypes',
+  'addons/documents/header/header.actiontypes',
+  "addons/documents/resources"
+],
+
+function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) {
+  var Stores = {};
+
+  /*TODO:
+    remove header code, add delete, clean up pagination tests
+    */
+
+  Stores.IndexResultsStore = FauxtonAPI.Store.extend({
+
+    initialize: function () {
+      this._deleteable = false;
+      this._collection = [];
+      this.clearSelectedItems();
+      this.clearCollapsedDocs();
+      this._isLoading = false;
+    },
+
+    clearSelectedItems: function () {
+      this._selectedItems = {};
+    },
+
+    clearCollapsedDocs: function () {
+      this._collapsedDocs = {};
+    },
+
+    newResults: function (options) {
+      this._collection = options.collection;
+      this._deleteable = options.deleteable;
+      this.clearSelectedItems();
+      this.clearCollapsedDocs();
+    },
+
+    hasReduce: function () {
+      if (!this._collection || !this._collection.params) {
+        return false;
+      }
+      return this._collection.params.reduce;
+    },
+
+    getCollection: function () {
+      return this._collection;
+    },
+
+    getDocContent: function (originalDoc) {
+      var doc = originalDoc.toJSON();
+
+      if (this.isCollapsed(doc._id)) {
+        doc = {
+          _id: _.isUndefined(doc._id),
+          _rev: doc._rev
+        };
+      }
+
+      return JSON.stringify(doc, null, "  ");
+    },
+
+    getDocId: function (doc) {
+
+      if (!_.isUndefined(doc.id)) {
+        return doc.id;
+      }
+
+      if (!_.isNull(doc.get('key'))) {
+        return doc.get('key').toString();
+      }
+
+      return '';
+    },
+
+    getResults: function () {
+      return this._collection.map(function (doc) {
+        return {
+          content: this.getDocContent(doc),
+          id: this.getDocId(doc),
+          keylabel: doc.isFromView() ? 'key' : 'id',
+          url: doc.isFromView() ? doc.url('app') : doc.url('web-index')
+        };
+      }, this);
+    },
+
+    hasResults: function () {
+      if (this._isLoading) { return this._isLoading; }
+      return this._collection.length > 0;
+    },
+
+    isDeleteable: function () {
+      return this._deleteable;
+    },
+
+    selectDoc: function (id) {
+      if (!this._selectedItems[id]) {
+        this._selectedItems[id] = true;
+      } else {
+        delete this._selectedItems[id];
+      }
+    },
+
+    selectListOfDocs: function (ids) {
+      this.clearSelectedItems();
+      _.each(ids, function (id) {
+        this.selectDoc(id);
+      }, this);
+    },
+
+    selectAllDocuments: function () {
+      this.clearSelectedItems();
+      this._collection.each(function (doc) {
+        this.selectDoc(doc.id);
+      }, this);
+    },
+
+    deSelectAllDocuments: function () {
+      this.clearSelectedItems();
+    },
+
+    getSelectedItemsLength: function () {
+      return _.keys(this._selectedItems).length;
+    },
+
+    getCollapsedDocsLength: function () {
+      return _.keys(this._collapsedDocs).length;
+    },
+
+    getCollapsedDocs: function () {
+      return this._collapsedDocs;
+    },
+
+    getDatabase: function () {
+      return this._collection.database;
+    },
+
+    createBulkDeleteFromSelected: function () {
+      var items = _.map(_.keys(this._selectedItems), function (id) {
+        var doc = this._collection.get(id);
+
+        return {
+          _id: doc.id,
+          _rev: doc.get('_rev'),
+          _deleted: true
+        };
+      }, this);
+
+      var bulkDelete = new Documents.BulkDeleteDocCollection(items, {
+        databaseId: this.getDatabase().safeID()
+      });
+
+      return bulkDelete;
+    },
+
+    canSelectAll: function () {
+      return this._collection.length > this.getSelectedItemsLength();
+    },
+
+    canDeselectAll: function () {
+      return this.getSelectedItemsLength() > 0;
+    },
+
+    getSelectedItems: function () {
+      return this._selectedItems;
+    },
+
+    canCollapseDocs: function () {
+      return this._collection.length > this.getCollapsedDocsLength();
+    },
+
+    canUncollapseDocs: function () {
+      return this.getCollapsedDocsLength() > 0;
+    },
+
+    isSelected: function (id) {
+      return !!this._selectedItems[id];
+    },
+
+    isCollapsed: function (id) {
+      return !!this._collapsedDocs[id];
+    },
+
+    collapseSelectedDocs: function () {
+      _.each(this._selectedItems, function (val, key) {
+        this._collapsedDocs[key] = true;
+      }, this);
+    },
+
+    unCollapseSelectedDocs: function () {
+      _.each(this._selectedItems, function (val, key) {
+        delete this._collapsedDocs[key];
+      }, this);
+    },
+
+    clearResultsBeforeFetch: function () {
+      this.getCollection().reset();
+      this._isLoading = true;
+    },
+
+    resultsResetFromFetch: function () {
+      this._isLoading = false;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.INDEX_RESULTS_NEW_RESULTS:
+          this.newResults(action.options);
+          this.triggerChange();
+        break;
+        case ActionTypes.INDEX_RESULTS_RESET:
+          this.resultsResetFromFetch();
+          this.triggerChange();
+        break;
+        case ActionTypes.INDEX_RESULTS_SELECT_DOC:
+          this.selectDoc(action.id);
+          this.triggerChange();
+        break;
+        case ActionTypes.INDEX_RESULTS_SELECT_LIST_OF_DOCS:
+          this.selectListOfDocs(action.ids);
+          this.triggerChange();
+        break;
+        case ActionTypes.INDEX_RESULTS_CLEAR_RESULTS:
+          this.clearResultsBeforeFetch();
+          this.triggerChange();
+        break;
+        case HeaderActionTypes.SELECT_ALL_DOCUMENTS:
+          this.selectAllDocuments();
+          this.triggerChange();
+        break;
+        case HeaderActionTypes.DESELECT_ALL_DOCUMENTS:
+          this.deSelectAllDocuments();
+          this.triggerChange();
+        break;
+        case HeaderActionTypes.COLLAPSE_DOCUMENTS:
+          this.collapseSelectedDocs();
+          this.triggerChange();
+        break;
+        case HeaderActionTypes.EXPAND_DOCUMENTS:
+          this.unCollapseSelectedDocs();
+          this.triggerChange();
+        break;
+        default:
+        return;
+        // do nothing
+      }
+    }
+
+  });
+
+  Stores.indexResultsStore = new Stores.IndexResultsStore();
+
+  Stores.indexResultsStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.indexResultsStore.dispatch);
+
+  return Stores;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/tests/index-results.actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/tests/index-results.actionsSpec.js b/app/addons/documents/index-results/tests/index-results.actionsSpec.js
new file mode 100644
index 0000000..5ec9aab
--- /dev/null
+++ b/app/addons/documents/index-results/tests/index-results.actionsSpec.js
@@ -0,0 +1,232 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/documents/index-results/actions',
+  'addons/documents/index-results/stores',
+  'addons/documents/header/header.stores',
+  'addons/documents/header/header.actions',
+  'addons/documents/resources',
+  'testUtils',
+], function (FauxtonAPI, Actions, Stores, HeaderStores, HeaderActions, Documents, testUtils) {
+  var assert = testUtils.assert;
+  var restore = testUtils.restore;
+  var store = Stores.indexResultsStore;
+
+  FauxtonAPI.router = new FauxtonAPI.Router([]);
+
+  describe('Index Results Actions', function () {
+
+    describe('#newResultsList', function () {
+
+      it('sends results list reset', function () {
+        var collection = {
+          fetch: function () {
+            var promise = $.Deferred();
+            promise.resolve();
+            return promise;
+          }
+        };
+
+        var spy = sinon.spy(Actions, 'resultsListReset');
+
+        Actions.newResultsList({collection: collection});
+        assert.ok(spy.calledOnce);
+      });
+
+    });
+
+  });
+
+  describe('#selectDoc', function () {
+    afterEach(function () {
+      restore(HeaderStores.headerBarStore.getToggleStatus);
+      restore(HeaderActions.toggleHeaderControls);
+    });
+
+    it('toggles header controls if not active', function () {
+      var stub = sinon.stub(HeaderStores.headerBarStore, 'getToggleStatus');
+      stub.returns(false);
+
+      var spy = sinon.spy(HeaderActions, 'toggleHeaderControls');
+
+      Actions.selectDoc('id');
+      assert.ok(spy.calledOnce);
+    });
+
+    it('does not toggles header controls if active', function () {
+      store.clearSelectedItems();
+      var stub = sinon.stub(HeaderStores.headerBarStore, 'getToggleStatus');
+      stub.returns(true);
+
+      var spy = sinon.spy(HeaderActions, 'toggleHeaderControls');
+
+      Actions.selectDoc('id');
+      assert.notOk(spy.calledOnce);
+    });
+
+    it('hides header control if active and no items selected', function () {
+      var stub = sinon.stub(HeaderStores.headerBarStore, 'getToggleStatus');
+      stub.returns(true);
+      store._selectedItems = {'id': true};
+
+      var spy = sinon.spy(HeaderActions, 'toggleHeaderControls');
+
+      Actions.selectDoc('id');
+      assert.ok(spy.calledOnce);
+
+    });
+
+  });
+
+  describe('#deleteSelected', function () {
+    var confirmStub;
+
+    beforeEach(function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store._selectedItems = {
+        'testId1': true,
+        'testId2': true
+      };
+
+      confirmStub = sinon.stub(window, 'confirm');
+      confirmStub.returns(true);
+
+    });
+
+    afterEach(function () {
+      restore(window.confirm);
+      restore(store.createBulkDeleteFromSelected);
+      restore(FauxtonAPI.addNotification);
+      restore(Actions.reloadResultsList);
+      restore(Actions.selectListOfDocs);
+    });
+
+    it('doesn\'t delete if user denies confirmation', function () {
+      window.confirm.restore();
+
+      var stub = sinon.stub(window, 'confirm');
+      stub.returns(false);
+
+      var spy = sinon.spy(store, 'createBulkDeleteFromSelected');
+
+      Actions.deleteSelected();
+
+      assert.notOk(spy.calledOnce);
+    });
+
+    it('creates bulk delete', function () {
+      var spy = sinon.spy(store, 'createBulkDeleteFromSelected');
+
+      Actions.deleteSelected();
+
+      assert.ok(spy.calledOnce);
+    });
+
+    it('on success notifies all deleted', function () {
+      var spy = sinon.spy(FauxtonAPI, 'addNotification');
+      var promise = FauxtonAPI.Deferred();
+      var ids = {
+          errorIds: []
+      };
+      var bulkDelete = {
+        bulkDelete: function () {
+          promise.resolve(ids);
+          return promise;
+        }
+      };
+      var stub = sinon.stub(store, 'createBulkDeleteFromSelected');
+      stub.returns(bulkDelete);
+
+      Actions.deleteSelected();
+
+      assert.ok(spy.calledOnce);
+    });
+
+    it('on success with some failed ids, re-selects failed', function () {
+      var spy = sinon.spy(Actions, 'selectListOfDocs');
+
+      var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList');
+      var stubPromise = FauxtonAPI.Deferred();
+      stubPromise.resolve();
+      reloadResultsListStub.returns(stubPromise);
+
+      var promise = FauxtonAPI.Deferred();
+      var ids = {
+          errorIds: ['1']
+      };
+      var bulkDelete = {
+        bulkDelete: function () {
+          promise.resolve(ids);
+          return promise;
+        }
+      };
+
+      var stub = sinon.stub(store, 'createBulkDeleteFromSelected');
+      stub.returns(bulkDelete);
+
+      Actions.deleteSelected();
+      assert.ok(spy.calledWith(ids.errorIds));
+    });
+
+    it('on failure notifies failed', function () {
+      var spy = sinon.spy(FauxtonAPI, 'addNotification');
+      var promise = FauxtonAPI.Deferred();
+      var bulkDelete = {
+        bulkDelete: function () {
+          promise.reject();
+          return promise;
+        }
+      };
+      var stub = sinon.stub(store, 'createBulkDeleteFromSelected');
+      stub.returns(bulkDelete);
+
+      Actions.deleteSelected();
+
+      assert.ok(spy.calledOnce);
+    });
+
+    it('on failure re-selects docs', function () {
+      var spy = sinon.spy(Actions, 'selectListOfDocs');
+
+      var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList');
+      var stubPromise = FauxtonAPI.Deferred();
+      stubPromise.resolve();
+      reloadResultsListStub.returns(stubPromise);
+
+      var promise = FauxtonAPI.Deferred();
+      var errorIds = ['1'];
+
+      var bulkDelete = {
+        bulkDelete: function () {
+          promise.reject(errorIds);
+          return promise;
+        }
+      };
+
+      var stub = sinon.stub(store, 'createBulkDeleteFromSelected');
+      stub.returns(bulkDelete);
+
+      Actions.deleteSelected();
+      assert.ok(spy.calledWith(errorIds));
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
new file mode 100644
index 0000000..4502921
--- /dev/null
+++ b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx
@@ -0,0 +1,31 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+define([
+  'api',
+  'addons/documents/index-results/index-results.components.react',
+  'testUtils',
+  "react"
+], function (FauxtonAPI, Views, utils, React) {
+  FauxtonAPI.router = new FauxtonAPI.Router([]);
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+
+  describe('Index Results', function () {
+    var container;
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/tests/index-results.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/tests/index-results.storesSpec.js b/app/addons/documents/index-results/tests/index-results.storesSpec.js
new file mode 100644
index 0000000..574082f
--- /dev/null
+++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js
@@ -0,0 +1,372 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/documents/index-results/stores',
+  'addons/documents/index-results/actiontypes',
+  'addons/documents/shared-resources',
+  'testUtils'
+], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) {
+  var assert = testUtils.assert;
+  var dispatchToken;
+  var store;
+
+  describe('Index Results Store', function () {
+
+    beforeEach(function () {
+      store = new Stores.IndexResultsStore();
+      dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch);
+    });
+
+    describe('#hasResults', function () {
+
+      it('returns true for collection', function () {
+        store._collection = [1, 2, 3];
+
+        assert.ok(store.hasResults());
+      });
+
+      it('returns false for empty collection', function () {
+        store._collection = [];
+
+        assert.notOk(store.hasResults());
+      });
+
+    });
+
+    describe('#getResults', function () {
+
+      it('has correct doc format', function () {
+        store._collection = new Documents.AllDocs([{_id: 'testId'}], {
+          params: {},
+          database: {
+            safeID: function () { return '1';}
+          }
+        });
+
+        var doc = store.getResults()[0];
+        assert.equal(doc.id, 'testId');
+        assert.equal(doc.keylabel, 'id');
+      });
+
+    });
+
+    afterEach(function () {
+      FauxtonAPI.dispatcher.unregister(dispatchToken);
+    });
+  });
+
+  describe('canSelectAll', function () {
+
+    it('returns true for selected docs less than collection', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store._selectedItems = {'testId1': true};
+      assert.ok(store.canSelectAll());
+    });
+
+    it('returns false for selected docs same as collection', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store._selectedItems = {
+        'testId1': true,
+        'testId2': true
+      };
+
+      assert.notOk(store.canSelectAll());
+    });
+
+  });
+
+  describe('canDeselectAll', function () {
+
+    it('returns true for selected docs', function () {
+      store._selectedItems = {'testId1': true};
+      assert.ok(store.canDeselectAll());
+    });
+
+    it('returns false for no selected docs', function () {
+      store._selectedItems = {};
+
+      assert.notOk(store.canDeselectAll());
+    });
+
+  });
+
+  describe('canCollapseDocs', function () {
+
+    it('returns true for no collapsed docs', function () {
+      store._collapsedDocs = {};
+      assert.ok(store.canCollapseDocs());
+    });
+
+    it('returns false for all collapsed docs', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store._collapsedDocs = {
+        'testId1': true,
+        'testId2': true
+      };
+
+      assert.notOk(store.canCollapseDocs());
+    });
+
+  });
+
+  describe('canUncollapseDocs', function () {
+
+    it('returns true for collapsed docs', function () {
+      store._collapsedDocs = {'testId1': true};
+      assert.ok(store.canUncollapseDocs());
+    });
+
+    it('returns false for no collapsed docs', function () {
+      store.clearCollapsedDocs();
+
+      assert.notOk(store.canUncollapseDocs());
+    });
+
+  });
+
+  describe('getDocContent', function () {
+
+    it('returns full doc if not collapsed', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      var doc = store._collection.first();
+      var result = store.getDocContent(doc);
+
+      assert.equal(JSON.parse(result).value, 'one');
+    });
+
+    it('returns collapsed doc if collapsed', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      var doc = store._collection.first();
+      store._collapsedDocs = {'testId1': true};
+      var result = store.getDocContent(doc);
+
+      assert.ok(_.isUndefined(JSON.parse(result).value));
+    });
+
+  });
+
+  describe('#selectDoc', function () {
+
+    it('selects doc if not already selected', function () {
+      store._selectedItems = {};
+      store.selectDoc('id');
+      assert.equal(store.getSelectedItemsLength(), 1);
+    });
+
+    it('deselects doc if already selected', function () {
+      store._selectedItems = {'id': true};
+      store.selectDoc('id');
+      assert.equal(store.getSelectedItemsLength(), 0);
+    });
+  });
+
+  describe('#selectAllDocuments', function () {
+
+    it('selects all documents', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store.selectAllDocuments();
+      assert.ok(store.getSelectedItems().testId1);
+    });
+
+  });
+
+  describe('#deSelectAllDocuments', function () {
+
+    it('deselects all documents', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store.selectAllDocuments();
+      assert.ok(store.getSelectedItems().testId1);
+      store.deSelectAllDocuments();
+      assert.equal(store.getSelectedItemsLength(), 0);
+    });
+  });
+
+  describe('#collapseSelectedDocs', function () {
+
+    it('collapses all selected docs', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store.clearCollapsedDocs();
+
+      store._selectedItems = {
+        'testId1': true,
+        'testId2': true
+      };
+
+      store.collapseSelectedDocs();
+      assert.equal(store.getCollapsedDocsLength(), 2);
+    });
+
+  });
+
+  describe('#unCollapseSelectedDocs', function () {
+
+    it('uncollapses all selected docs', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store.clearCollapsedDocs();
+
+      store._selectedItems = {
+        'testId1': true,
+        'testId2': true
+      };
+
+      store.collapseSelectedDocs();
+      assert.equal(store.getCollapsedDocsLength(), 2);
+      store.unCollapseSelectedDocs();
+      assert.equal(store.getCollapsedDocsLength(), 0);
+    });
+  });
+
+  describe('#createBulkDeleteFromSelected', function () {
+
+    it('correctly creates BulkDeleteDocCollection', function () {
+      store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], {
+        params: {},
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      store._selectedItems = {
+        'testId1': true,
+        'testId2': true
+      };
+
+      var bulkDelete = store.createBulkDeleteFromSelected();
+
+      assert.equal(bulkDelete.length, 2);
+      assert.ok(bulkDelete.at(0).get('_deleted'));
+    });
+
+  });
+
+  describe('#getDocId', function () {
+
+    it('returns id if it exists', function () {
+      var doc = new Documents.Doc({
+        _id: 'doc-id'
+      }, {
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      assert.equal(store.getDocId(doc), 'doc-id');
+
+    });
+
+    it('returns key if it exists', function () {
+      var doc = new Documents.Doc({
+        key: 'doc-key'
+      }, {
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      assert.equal(store.getDocId(doc), 'doc-key');
+
+    });
+
+    it('returns empty string if no key or id exists', function () {
+      var doc = new Documents.Doc({
+        key: null,
+        value: 'the-value'
+      }, {
+        database: {
+          safeID: function () { return '1';}
+        }
+      });
+
+      assert.equal(store.getDocId(doc), '');
+
+    });
+  });
+
+  describe('hasReduce', function () {
+
+    it('returns false for no collection', function () {
+      store._collection = null;
+      assert.notOk(store.hasReduce());
+    });
+
+    it('returns false for no params', function () {
+      store._collection = [];
+      assert.notOk(store.hasReduce());
+    });
+
+    it('returns true for reduce param', function () {
+      store._collection = [];
+      store._collection.param = {
+        reduce: true
+      };
+      assert.notOk(store.hasReduce());
+
+    });
+
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/pagination/actions.js b/app/addons/documents/pagination/actions.js
index c69087a..5705d90 100644
--- a/app/addons/documents/pagination/actions.js
+++ b/app/addons/documents/pagination/actions.js
@@ -14,9 +14,10 @@ define([
   'app',
   'api',
   'addons/documents/pagination/actiontypes',
-  'addons/documents/pagination/stores'
+  'addons/documents/pagination/stores',
+  'addons/documents/index-results/actions'
 ],
-function (app, FauxtonAPI, ActionTypes, Stores) {
+function (app, FauxtonAPI, ActionTypes, Stores, IndexResultsActions) {
 
   var store = Stores.indexPaginationStore;
 
@@ -27,13 +28,10 @@ function (app, FauxtonAPI, ActionTypes, Stores) {
         perPage: perPage
       });
 
-      FauxtonAPI.triggerRouteEvent('perPageChange', store.documentsLeftToFetch());
-    },
+      IndexResultsActions.clearResults();
 
-    newPagination: function (collection) {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.NEW_PAGINATION,
-        collection: collection
+      store.getCollection().fetch().then(function () {
+        IndexResultsActions.resultsListReset();
       });
     },
 
@@ -44,21 +42,13 @@ function (app, FauxtonAPI, ActionTypes, Stores) {
       });
     },
 
-    collectionReset: function () {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.PAGINATION_COLLECTION_RESET,
-      });
-    },
-
     paginateNext: function () {
       FauxtonAPI.dispatch({
         type: ActionTypes.PAGINATE_NEXT,
       });
 
-      FauxtonAPI.triggerRouteEvent('paginate', {
-       direction: 'next',
-       perPage: store.documentsLeftToFetch(),
-       currentPage: store.getCurrentPage()
+      store.getCollection().next().then(function () {
+        IndexResultsActions.resultsListReset();
       });
     },
 
@@ -67,10 +57,8 @@ function (app, FauxtonAPI, ActionTypes, Stores) {
         type: ActionTypes.PAGINATE_PREVIOUS,
       });
 
-      FauxtonAPI.triggerRouteEvent('paginate', {
-       direction: 'previous',
-       perPage: store.getPerPage(),
-       currentPage: store.getCurrentPage()
+      store.getCollection().previous().then(function () {
+        IndexResultsActions.resultsListReset();
       });
     },
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/pagination/actiontypes.js b/app/addons/documents/pagination/actiontypes.js
index fe2fe93..199131e 100644
--- a/app/addons/documents/pagination/actiontypes.js
+++ b/app/addons/documents/pagination/actiontypes.js
@@ -14,8 +14,6 @@ define([], function () {
   return {
     COLLECTION_CHANGED: 'COLLECTION_CHANGED',
     PER_PAGE_CHANGE: 'PER_PAGE_CHANGE',
-    NEW_PAGINATION: 'NEW_PAGINATION',
-    COLLECTION_RESET: 'PAGINATION_COLLECTION_RESET',
     PAGINATE_NEXT: 'PAGINATE_NEXT',
     PAGINATE_PREVIOUS: 'PAGINATE_PREVIOUS',
     SET_PAGINATION_DOCUMENT_LIMIT: 'SET_PAGINATION_DOCUMENT_LIMIT'

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/pagination/stores.js b/app/addons/documents/pagination/stores.js
index f63f766..731614a 100644
--- a/app/addons/documents/pagination/stores.js
+++ b/app/addons/documents/pagination/stores.js
@@ -13,8 +13,9 @@
 define([
   'app',
   'api',
-  'addons/documents/pagination/actiontypes'
-], function (app, FauxtonAPI, ActionTypes) {
+  'addons/documents/pagination/actiontypes',
+  'addons/documents/index-results/actiontypes'
+], function (app, FauxtonAPI, ActionTypes, IndexResultsActionTypes) {
 
   var Stores = {};
   var maxDocLimit = 10000;
@@ -49,6 +50,10 @@ define([
       this.reset();
     },
 
+    getCollection: function () {
+      return this._collection;
+    },
+
     canShowPrevious: function () {
       if (!this._enabled) { return false; }
       return this._collection.hasPrevious();
@@ -67,6 +72,7 @@ define([
     paginateNext: function () {
       this._currentPage += 1;
       this._pageStart += this.getPerPage();
+      this._collection.paging.pageSize = this.documentsLeftToFetch();
     },
 
     paginatePrevious: function () {
@@ -76,6 +82,8 @@ define([
       if (this._pageStart < 1) {
         this._pageStart = 1;
       }
+
+      this._collection.paging.pageSize = this.getPerPage();
     },
 
     getCurrentPage: function () {
@@ -121,6 +129,10 @@ define([
     setPerPage: function (perPage) {
       this._perPage = perPage;
       app.utils.localStorageSet('fauxton:perpage', perPage);
+
+      if (this._collection && this._collection.pageSizeReset) {
+        this._collection.pageSizeReset(perPage, {fetch: false});
+      }
     },
 
     getTotalRows: function () {
@@ -142,15 +154,15 @@ define([
     dispatch: function (action) {
 
       switch (action.type) {
-        case ActionTypes.NEW_PAGINATION:
-          this.newPagination(action.collection);
+        case IndexResultsActionTypes.INDEX_RESULTS_NEW_RESULTS:
+          this.newPagination(action.options.collection);
           this.triggerChange();
         break;
         case ActionTypes.SET_PAGINATION_DOCUMENT_LIMIT:
           this.setDocumentLimit(action.docLimit);
           this.triggerChange();
         break;
-        case ActionTypes.PAGINATION_COLLECTION_RESET:
+        case IndexResultsActionTypes.INDEX_RESULTS_RESET:
           this.triggerChange();
         break;
         case ActionTypes.PAGINATE_NEXT:

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/tests/pagination.actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/pagination/tests/pagination.actionsSpec.js b/app/addons/documents/pagination/tests/pagination.actionsSpec.js
index 8eab4b2..edeff05 100644
--- a/app/addons/documents/pagination/tests/pagination.actionsSpec.js
+++ b/app/addons/documents/pagination/tests/pagination.actionsSpec.js
@@ -14,8 +14,10 @@ define([
   'api',
   'addons/documents/pagination/actions',
   'addons/documents/pagination/stores',
+  'addons/documents/index-results/actions',
+  'addons/documents/shared-resources',
   'testUtils',
-], function (FauxtonAPI, Actions, Stores, testUtils) {
+], function (FauxtonAPI, Actions, Stores, IndexResultsActions, Documents, testUtils) {
   var assert = testUtils.assert;
 
   FauxtonAPI.router = new FauxtonAPI.Router([]);
@@ -26,47 +28,74 @@ define([
       Stores.indexPaginationStore.documentsLeftToFetch.restore && Stores.indexPaginationStore.documentsLeftToFetch.restore();
       Stores.indexPaginationStore.getCurrentPage.restore && Stores.indexPaginationStore.getCurrentPage.restore();
       Stores.indexPaginationStore.getPerPage.restore && Stores.indexPaginationStore.getPerPage.restore();
-      FauxtonAPI.triggerRouteEvent.restore();
+
+      IndexResultsActions.resultsListReset.restore && IndexResultsActions.resultsListReset.restore();
+      Stores.indexPaginationStore.getCollection.restore && Stores.indexPaginationStore.getCollection.restore();
     });
 
     describe('updatePerPage', function () {
 
-      it('triggers routeEvent', function () {
-        var stub = sinon.stub(Stores.indexPaginationStore, 'documentsLeftToFetch');
-        stub.returns(30);
-        var spy = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
+      beforeEach(function () {
+        Stores.indexPaginationStore._collection = new Documents.AllDocs([{id:1}, {id: 2}], {
+          params: {},
+          database: {
+            safeID: function () { return '1';}
+          }
+        });
+
+      });
+
+      it('fetches collection', function () {
+        var spy = sinon.spy(Stores.indexPaginationStore, 'getCollection');
         Actions.updatePerPage(30);
 
-        assert.ok(spy.calledWith('perPageChange', 30));
+        assert.ok(spy.calledOnce);
+      });
+
+      it('sends results list reset', function () {
+        var promise = $.Deferred();
+        promise.resolve();
+        var stub = sinon.stub(Stores.indexPaginationStore, 'getCollection');
+        var spy = sinon.spy(IndexResultsActions, 'resultsListReset');
+        stub.returns({
+          fetch: function () { return promise; }
+        });
+
+        Actions.updatePerPage(30);
+        assert.ok(spy.calledOnce);
       });
     });
 
     describe('paginateNext', function () {
 
-      it('triggers routeEvent', function () {
-        var spyEvent = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
-        var spyDocuments = sinon.spy(Stores.indexPaginationStore, 'documentsLeftToFetch');
-        var spyPage = sinon.spy(Stores.indexPaginationStore, 'getCurrentPage');
-        Actions.paginateNext();
+      it('sends results list reset', function () {
+        var promise = $.Deferred();
+        promise.resolve();
+        var stub = sinon.stub(Stores.indexPaginationStore, 'getCollection');
+        var spy = sinon.spy(IndexResultsActions, 'resultsListReset');
+        stub.returns({
+          next: function () { return promise; }
+        });
 
-        assert.ok(spyEvent.calledOnce);
-        assert.ok(spyDocuments.calledOnce);
-        assert.ok(spyPage.calledOnce);
+        Actions.paginateNext();
+        assert.ok(spy.calledOnce);
       });
 
     });
 
     describe('paginatePrevious', function () {
 
-      it('triggers routeEvent', function () {
-        var spyEvent = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
-        var spyPerPage = sinon.spy(Stores.indexPaginationStore, 'getPerPage');
-        var spyPage = sinon.spy(Stores.indexPaginationStore, 'getCurrentPage');
-        Actions.paginatePrevious();
+      it('sends results list reset', function () {
+        var promise = $.Deferred();
+        promise.resolve();
+        var stub = sinon.stub(Stores.indexPaginationStore, 'getCollection');
+        var spy = sinon.spy(IndexResultsActions, 'resultsListReset');
+        stub.returns({
+          previous: function () { return promise; }
+        });
 
-        assert.ok(spyEvent.calledOnce);
-        assert.ok(spyPerPage.called);
-        assert.ok(spyPage.calledOnce);
+        Actions.paginatePrevious();
+        assert.ok(spy.calledOnce);
       });
 
     });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/tests/paginationStoreSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/pagination/tests/paginationStoreSpec.js b/app/addons/documents/pagination/tests/paginationStoreSpec.js
index 2230af5..f278c3d 100644
--- a/app/addons/documents/pagination/tests/paginationStoreSpec.js
+++ b/app/addons/documents/pagination/tests/paginationStoreSpec.js
@@ -14,8 +14,9 @@ define([
   'api',
   'addons/documents/pagination/stores',
   'addons/documents/pagination/actiontypes',
+  'addons/documents/shared-resources',
   'testUtils'
-], function (FauxtonAPI, Stores, ActionTypes, testUtils) {
+], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) {
   var assert = testUtils.assert;
   var dispatchToken;
   var store;
@@ -34,7 +35,12 @@ define([
     describe('#collectionChanged', function () {
       var collection;
       beforeEach(function () {
-        collection = new Backbone.Collection([{id:1}, {id: 2}]);
+        collection = new Documents.AllDocs([{id:1}, {id: 2}], {
+          params: {},
+          database: {
+            safeID: function () { return '1';}
+          }
+        });
         collection.updateSeq = function () { return 'updateSeq';};
         store.reset();
       });
@@ -93,6 +99,12 @@ define([
     describe('paginateNext', function () {
       beforeEach(function () {
         store.setPerPage(20);
+        store._collection = new Documents.AllDocs(null, {
+          params: {},
+          database: {
+            safeID: function () { return '1';}
+          }
+        });
       });
 
       it('should increment page number', function () {
@@ -112,18 +124,30 @@ define([
       });
 
       it('should set correct page end', function () {
-        store._collection = new Backbone.Collection();
         store._collection.length = 20;
         store.reset();
         store.paginateNext();
 
         assert.equal(store.getPageEnd(), 40);
       });
+
+      it('should set collection pageSize', function () {
+        store.reset();
+        store.paginateNext();
+
+        assert.equal(store.getCollection().paging.pageSize, 20);
+      });
     });
 
     describe('paginatePrevious', function () {
       beforeEach(function () {
         store.reset();
+        store._collection = new Documents.AllDocs(null, {
+          params: {},
+          database: {
+            safeID: function () { return '1';}
+          }
+        });
       });
 
       it('should decrement page number', function () {
@@ -141,7 +165,6 @@ define([
       });
 
       it('should decrement page end', function () {
-        store._collection = new Backbone.Collection();
         store._collection.length = 20;
         store.paginateNext();
         store.paginatePrevious();
@@ -149,6 +172,14 @@ define([
         assert.equal(store.getPageEnd(), 20);
       });
 
+      it('should set collection pageSize', function () {
+        store.reset();
+        store.paginateNext();
+        store.paginatePrevious();
+
+        assert.equal(store.getCollection().paging.pageSize, 20);
+      });
+
     });
 
     describe('totalDocsViewed', function () {
@@ -226,7 +257,36 @@ define([
         store.setDocumentLimit(NaN);
         assert.equal(store._docLimit, 10000);
       });
+    });
+
+    describe('#setPerPage', function () {
+      beforeEach(function () {
+        store.reset();
+        store._collection = new Documents.AllDocs(null, {
+          params: {},
+          database: {
+            safeID: function () { return '1';}
+          }
+        });
+
+      });
 
+      it('stores per page in local storage', function () {
+        var testPerPage = 111;
+        store.setPerPage(testPerPage);
+        var perPage = window.localStorage.getItem('fauxton:perpage');
+        assert.equal(perPage, testPerPage );
+      });
+
+      it('sets collections perPage', function () {
+        var spy = sinon.spy(store._collection, 'pageSizeReset');
+        var testPerPage = 110;
+
+        store.setPerPage(testPerPage);
+        assert.equal(spy.getCall(0).args[0], testPerPage);
+
+
+      });
     });
   });
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index 880cce1..e6ee676 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -106,6 +106,7 @@ function (app, FauxtonAPI, Documents, PagingCollection) {
 
     bulkDelete: function () {
       var payload = this.createPayload(this.toJSON()),
+          promise = FauxtonAPI.Deferred(),
           that = this;
 
       $.ajax({
@@ -116,7 +117,7 @@ function (app, FauxtonAPI, Documents, PagingCollection) {
         data: JSON.stringify(payload),
       })
       .then(function (res) {
-        that.handleResponse(res);
+        that.handleResponse(res, promise);
       })
       .fail(function () {
         var ids = _.reduce(that.toArray(), function (acc, doc) {
@@ -124,10 +125,13 @@ function (app, FauxtonAPI, Documents, PagingCollection) {
           return acc;
         }, []);
         that.trigger('error', ids);
+        promise.reject(ids);
       });
+
+      return promise;
     },
 
-    handleResponse: function (res) {
+    handleResponse: function (res, promise) {
       var ids = _.reduce(res, function (ids, doc) {
         if (doc.error) {
           ids.errorIds.push(doc.id);
@@ -146,6 +150,14 @@ function (app, FauxtonAPI, Documents, PagingCollection) {
         this.trigger('error', ids.errorIds);
       }
 
+      // This is kind of tricky. If there are no documents deleted then rejects
+      // otherwise resolve with list of successful and failed documents
+      if (!_.isEmpty(ids.successIds)) {
+        promise.resolve(ids);
+      } else {
+        promise.reject(ids.errorIds);
+      }
+
       this.trigger('updated');
     },
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/routes-documents.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index 0b607de..ff57dac 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -24,225 +24,220 @@ define([
   'addons/databases/base',
   'addons/documents/resources',
   'addons/fauxton/components',
-  'addons/documents/pagination/actions',
-  'addons/documents/pagination/stores'
+  'addons/documents/pagination/stores',
+  'addons/documents/index-results/actions'
 ],
 
 function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
-        Databases, Resources, Components, PaginationActions, PaginationStores) {
+  Databases, Resources, Components, PaginationStores, IndexResultsActions) {
+
+
+    var DocumentsRouteObject = BaseRoute.extend({
+      layout: "with_tabs_sidebar",
+      routes: {
+        "database/:database/_all_docs(:extra)": {
+          route: "allDocs",
+          roles: ["fx_loggedIn"]
+        },
+        "database/:database/_design/:ddoc/_info": {
+          route: "designDocMetadata",
+          roles: ['fx_loggedIn']
+        },
+        'database/:database/_changes': 'changes'
+      },
 
+      events: {
+        "route:reloadDesignDocs": "reloadDesignDocs"
+      },
 
-  var DocumentsRouteObject = BaseRoute.extend({
-    layout: "with_tabs_sidebar",
-    routes: {
-      "database/:database/_all_docs(:extra)": {
-        route: "allDocs",
-        roles: ["fx_loggedIn"]
+      initialize: function (route, masterLayout, options) {
+        this.initViews(options[0]);
+        this.listenToLookaheadTray();
       },
-      "database/:database/_design/:ddoc/_info": {
-        route: "designDocMetadata",
-        roles: ['fx_loggedIn']
+
+      establish: function () {
+        return [
+          this.designDocs.fetch({reset: true}),
+          this.allDatabases.fetchOnce()
+        ];
       },
-      'database/:database/_changes': 'changes'
-    },
-
-    events: {
-      "route:reloadDesignDocs": "reloadDesignDocs",
-      'route:updateAllDocs': 'updateAllDocsFromView',
-      'route:paginate': 'paginate',
-      'route:perPageChange': 'perPageChange',
-    },
-
-    initialize: function (route, masterLayout, options) {
-      this.initViews(options[0]);
-      this.listenToLookaheadTray();
-    },
-
-    establish: function () {
-      return [
-        this.designDocs.fetch({reset: true}),
-        this.allDatabases.fetchOnce()
-      ];
-    },
-
-    initViews: function (dbName) {
-      this.databaseName = dbName;
-      this.database = new Databases.Model({id: this.databaseName});
-      this.allDatabases = this.getAllDatabases();
-
-      this.createDesignDocsCollection();
-
-      this.rightHeader = this.setView("#right-header", new Documents.Views.RightAllDocsHeader({
-        database: this.database
-      }));
-
-      this.addLeftHeader();
-      this.addSidebar();
-    },
-
-    getAllDatabases: function () {
-      return new Databases.List();  //getAllDatabases() can be overwritten instead of hard coded into initViews
-    },
-
-    // this safely assumes the db name is valid
-    onSelectDatabase: function (dbName) {
-      this.cleanup();
-      this.initViews(dbName);
-
-      var url = FauxtonAPI.urls('allDocs', 'app',  app.utils.safeURLName(dbName), '');
-      FauxtonAPI.navigate(url, {
-        trigger: true
-      });
-
-      // we need to start listening again because cleanup() removed the listener, but in this case
-      // initialize() doesn't fire to re-set up the listener
-      this.listenToLookaheadTray();
-    },
-
-    listenToLookaheadTray: function () {
-      this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase);
-    },
-
-    designDocMetadata: function (database, ddoc) {
-      this.footer && this.footer.remove();
-      this.toolsView && this.toolsView.remove();
-      this.viewEditor && this.viewEditor.remove();
-
-      var designDocInfo = new Resources.DdocInfo({ _id: "_design/" + ddoc }, { database: this.database });
-      this.setView("#dashboard-lower-content", new Documents.Views.DdocInfo({
-        ddocName: ddoc,
-        model: designDocInfo
-      }));
-
-      this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + "_metadata");
-      this.leftheader.updateCrumbs(this.getCrumbs(this.database));
-      this.rightHeader.hideQueryOptions();
-
-      this.apiUrl = [designDocInfo.url('apiurl'), designDocInfo.documentation()];
-    },
-
-    /*
-     * 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 params = this.createParams(options),
-          urlParams = params.urlParams,
-          docParams = params.docParams,
-          collection;
-
-      if (this.eventAllDocs) {
-        this.eventAllDocs = false;
-        return;
-      }
 
-      this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar());
+      initViews: function (dbName) {
+        this.databaseName = dbName;
+        this.database = new Databases.Model({id: this.databaseName});
+        this.allDatabases = this.getAllDatabases();
+
+        this.createDesignDocsCollection();
 
-      this.footer = this.setView('#footer', new Documents.Views.Footer());
+        this.rightHeader = this.setView("#right-header", new Documents.Views.RightAllDocsHeader({
+          database: this.database
+        }));
 
-      this.leftheader.updateCrumbs(this.getCrumbs(this.database));
+        this.addLeftHeader();
+        this.addSidebar();
+      },
 
-      this.database.buildAllDocs(docParams);
-      collection = this.database.allDocs;
+      getAllDatabases: function () {
+        return new Databases.List();  //getAllDatabases() can be overwritten instead of hard coded into initViews
+      },
 
-      if (docParams.startkey && docParams.startkey.indexOf("_design") > -1) {
-        this.sidebar.setSelectedTab("design-docs");
-      } else {
-        this.sidebar.setSelectedTab("all-docs");
-      }
+      // this safely assumes the db name is valid
+      onSelectDatabase: function (dbName) {
+        this.cleanup();
+        this.initViews(dbName);
 
-      this.viewEditor && this.viewEditor.remove();
-      this.headerView && this.headerView.remove();
+        var url = FauxtonAPI.urls('allDocs', 'app',  app.utils.safeURLName(dbName), '');
+        FauxtonAPI.navigate(url, {
+          trigger: true
+        });
 
+        // we need to start listening again because cleanup() removed the listener, but in this case
+        // initialize() doesn't fire to re-set up the listener
+        this.listenToLookaheadTray();
+      },
 
-      if (!docParams) {
-        docParams = {};
-      }
+      listenToLookaheadTray: function () {
+        this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase);
+      },
 
-      PaginationActions.newPagination(collection);
-      this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage();
+      designDocMetadata: function (database, ddoc) {
+        this.footer && this.footer.remove();
+        this.toolsView && this.toolsView.remove();
+        this.viewEditor && this.viewEditor.remove();
 
-      // documentsView will populate the collection
-      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
-        database: this.database,
-        collection: collection,
-        docParams: docParams,
-        bulkDeleteDocsCollection: new Documents.BulkDeleteDocCollection([], {databaseId: this.database.get('id')})
-      }));
+        var designDocInfo = new Resources.DdocInfo({ _id: "_design/" + ddoc }, { database: this.database });
+        this.setView("#dashboard-lower-content", new Documents.Views.DdocInfo({
+          ddocName: ddoc,
+          model: designDocInfo
+        }));
 
-      // this used to be a function that returned the object, but be warned: it caused a closure with a reference to
-      // the initial this.database object which can change
-      this.apiUrl = [this.database.allDocs.urlRef("apiurl", urlParams), this.database.allDocs.documentation()];
+        this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + "_metadata");
+        this.leftheader.updateCrumbs(this.getCrumbs(this.database));
+        this.rightHeader.hideQueryOptions();
 
-      // update the rightHeader with the latest & greatest info
-      this.rightHeader.resetQueryOptions({ queryParams: urlParams });
-      this.rightHeader.showQueryOptions();
-    },
+        this.apiUrl = [designDocInfo.url('apiurl'), designDocInfo.documentation()];
+      },
 
-    reloadDesignDocs: function (event) {
-      this.sidebar.forceRender();
+      /*
+      * 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 params = this.createParams(options),
+        urlParams = params.urlParams,
+        docParams = params.docParams,
+        collection;
 
-      if (event && event.selectedTab) {
-        this.sidebar.setSelectedTab(event.selectedTab);
-      }
-    },
+        if (this.eventAllDocs) {
+          this.eventAllDocs = false;
+          return;
+        }
 
-    changes: function () {
-      var docParams = app.getParams();
-      this.database.buildChanges(docParams);
+        this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar());
 
-      this.changesView = this.setView("#dashboard-lower-content", new Changes.ChangesReactWrapper({
-        model: this.database
-      }));
+        this.footer = this.setView('#footer', new Documents.Views.Footer());
 
-      this.headerView = this.setView('#dashboard-upper-content', new Changes.ChangesHeaderReactWrapper());
+        this.leftheader.updateCrumbs(this.getCrumbs(this.database));
 
-      this.footer && this.footer.remove();
-      this.toolsView && this.toolsView.remove();
-      this.viewEditor && this.viewEditor.remove();
-      this.reactHeader && this.reactHeader.remove();
+        this.database.buildAllDocs(docParams);
+        collection = this.database.allDocs;
 
-      this.sidebar.setSelectedTab('changes');
-      this.leftheader.updateCrumbs(this.getCrumbs(this.database));
-      this.rightHeader.hideQueryOptions();
+        if (docParams.startkey && docParams.startkey.indexOf("_design") > -1) {
+          this.sidebar.setSelectedTab("design-docs");
+        } else {
+          this.sidebar.setSelectedTab("all-docs");
+        }
 
-      this.apiUrl = function () {
-        return [FauxtonAPI.urls('changes', 'apiurl', this.database.id, ''), this.database.documentation()];
-      };
-    },
+        this.viewEditor && this.viewEditor.remove();
+        this.headerView && this.headerView.remove();
 
-    cleanup: function () {
-      if (this.reactHeader) {
-        this.removeView('#react-headerbar');
-      }
-      if (this.viewEditor) {
-        this.removeView('#dashboard-upper-content');
-      }
-      if (this.documentsView) {
-        this.removeView('#dashboard-lower-content');
-      }
-      if (this.rightHeader) {
-        this.removeView('#right-header');
-      }
-      if (this.leftheader) {
-        this.removeView('#breadcrumbs');
-      }
-      if (this.sidebar) {
-        this.removeView('#sidebar');
-      }
-      if (this.footer) {
-        this.removeView('#footer');
-      }
-      if (this.headerView) {
-        this.removeView('#dashboard-upper-content');
+
+        if (!docParams) {
+          docParams = {};
+        }
+
+        IndexResultsActions.newResultsList({
+          collection: collection,
+          deleteable: true
+        });
+
+        this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage();
+
+        this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({}));
+
+        // this used to be a function that returned the object, but be warned: it caused a closure with a reference to
+        // the initial this.database object which can change
+        this.apiUrl = [this.database.allDocs.urlRef("apiurl", urlParams), this.database.allDocs.documentation()];
+
+        // update the rightHeader with the latest & greatest info
+        this.rightHeader.resetQueryOptions({ queryParams: urlParams });
+        this.rightHeader.showQueryOptions();
+      },
+
+      reloadDesignDocs: function (event) {
+        this.sidebar.forceRender();
+
+        if (event && event.selectedTab) {
+          this.sidebar.setSelectedTab(event.selectedTab);
+        }
+      },
+
+      changes: function () {
+        var docParams = app.getParams();
+        this.database.buildChanges(docParams);
+
+        this.changesView = this.setView("#dashboard-lower-content", new Changes.ChangesReactWrapper({
+          model: this.database
+        }));
+
+        this.headerView = this.setView('#dashboard-upper-content', new Changes.ChangesHeaderReactWrapper());
+
+        this.footer && this.footer.remove();
+        this.toolsView && this.toolsView.remove();
+        this.viewEditor && this.viewEditor.remove();
+        this.reactHeader && this.reactHeader.remove();
+
+        this.sidebar.setSelectedTab('changes');
+        this.leftheader.updateCrumbs(this.getCrumbs(this.database));
+        this.rightHeader.hideQueryOptions();
+
+        this.apiUrl = function () {
+          return [FauxtonAPI.urls('changes', 'apiurl', this.database.id, ''), this.database.documentation()];
+        };
+      },
+
+      cleanup: function () {
+        if (this.reactHeader) {
+          this.removeView('#react-headerbar');
+        }
+        if (this.viewEditor) {
+          this.removeView('#dashboard-upper-content');
+        }
+        if (this.documentsView) {
+          this.removeView('#dashboard-lower-content');
+        }
+        if (this.rightHeader) {
+          this.removeView('#right-header');
+        }
+        if (this.leftheader) {
+          this.removeView('#breadcrumbs');
+        }
+        if (this.sidebar) {
+          this.removeView('#sidebar');
+        }
+        if (this.footer) {
+          this.removeView('#footer');
+        }
+        if (this.headerView) {
+          this.removeView('#dashboard-upper-content');
+        }
+
+        // we're no longer interested in listening to the lookahead tray event on this route object
+        this.stopListening(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase);
       }
 
-      // we're no longer interested in listening to the lookahead tray event on this route object
-      this.stopListening(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase);
-    }
+    });
 
+    return DocumentsRouteObject;
   });
-
-  return DocumentsRouteObject;
-});