couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From robertkowal...@apache.org
Subject [1/2] fauxton commit: updated refs/heads/master to f5f7f4b
Date Wed, 28 Jan 2015 17:36:21 GMT
Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 16ec9ab45 -> f5f7f4b93


all-docs: New way to bulk-edit documents

adds a bar with controls for interaction with documents to the
upper header which can be toggled on and off

the button for the bar toggles between a header with the api-bar,
query-options-tray and search and the new header for selecting
and deselecting all documents, collapsing and deleting them


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

Branch: refs/heads/master
Commit: 25dab477415d417019cf863db393470b11e5d350
Parents: 16ec9ab
Author: Robert Kowalski <robertkowalski@apache.org>
Authored: Mon Jan 19 13:58:51 2015 +0100
Committer: Robert Kowalski <robertkowalski@apache.org>
Committed: Wed Jan 28 18:31:02 2015 +0100

----------------------------------------------------------------------
 app/addons/documents/assets/less/documents.less |   6 +
 app/addons/documents/assets/less/header.less    |  53 ++++
 app/addons/documents/header/header.actions.js   |  61 +++++
 .../documents/header/header.actiontypes.js      |  22 ++
 app/addons/documents/header/header.react.jsx    | 245 +++++++++++++++++++
 app/addons/documents/header/header.stores.js    | 150 ++++++++++++
 app/addons/documents/routes-documents.js        |   5 +
 .../documents/templates/all_docs_list.html      |  14 --
 app/addons/documents/tests/headerSpec.react.jsx | 146 +++++++++++
 .../documents/tests/nightwatch/bulkDelete.js    |  16 +-
 .../tests/nightwatch/deletesDocument.js         |   7 +-
 app/addons/documents/tests/viewsSpec.js         |  49 +---
 app/addons/documents/views.js                   |  92 ++++---
 app/app.js                                      |   1 -
 app/constants.js                                |   2 +-
 app/templates/layouts/with_tabs_sidebar.html    |   1 +
 assets/less/animations.less                     |  14 ++
 assets/less/fauxton.less                        |   2 +-
 assets/less/react-animations.less               |  17 ++
 19 files changed, 793 insertions(+), 110 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/assets/less/documents.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/documents.less b/app/addons/documents/assets/less/documents.less
index ed28272..4e53065 100644
--- a/app/addons/documents/assets/less/documents.less
+++ b/app/addons/documents/assets/less/documents.less
@@ -18,6 +18,8 @@
 @import "changes.less";
 @import "sidenav.less";
 
+@import "header.less";
+
 button.beautify {
   margin-top: 20px;
 }
@@ -226,3 +228,7 @@ button.string-edit[disabled] {
     }
   }
 }
+
+#react-headerbar {
+  float: left;
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/assets/less/header.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/header.less b/app/addons/documents/assets/less/header.less
new file mode 100644
index 0000000..0bf0c3a
--- /dev/null
+++ b/app/addons/documents/assets/less/header.less
@@ -0,0 +1,53 @@
+.header-control-box {
+  color: #666;
+  font-size: 13px;
+  line-height: 33px;
+  padding: 12px 20px 12px 20px;
+  height: @collapsedNavWidth;
+  background-color: transparent;
+  border: none;
+  border-right: 1px solid @btnBorder;
+  text-decoration: none;
+  float: left;
+  .icon {
+    font-size: 20px;
+  }
+}
+
+button.header-control-box:focus {
+  outline: 0;
+}
+
+.js-headerbar-togglebutton-selected {
+  .icon, span {
+    color: #e33f3b;
+  }
+}
+
+.control-toggle-alternative-header {
+  .icon {
+    padding: 0px 8px 0px 0px;
+  }
+}
+
+.control-toggle-alternative-header:hover,
+.control-cancel:hover,
+.control-delete:hover {
+  .icon, span {
+    color: #e33f3b;
+  }
+}
+
+.control-cancel {
+  border-right: none;
+}
+
+.header-control-square {
+  height: @collapsedNavWidth;
+  width: @collapsedNavWidth;
+  float: left;
+}
+
+.alternative-header {
+  float: left;
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/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
new file mode 100644
index 0000000..7be2a05
--- /dev/null
+++ b/app/addons/documents/header/header.actions.js
@@ -0,0 +1,61 @@
+// 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/header/header.actiontypes'
+],
+function (app, FauxtonAPI, ActionTypes) {
+
+  return {
+    toggleCollapseDocuments: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.COLLAPSE_DOCUMENTS
+      });
+
+      FauxtonAPI.Events.trigger('headerbar:collapse');
+    },
+
+    toggleSelectAllDocuments: function (on) {
+      FauxtonAPI.Events.trigger('headerbar:selectall', on);
+    },
+
+    updateDocumentCount: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.UPDATE_DOCUMENT_COUNT,
+        options: options
+      });
+    },
+
+    deleteSelected: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.DELETE_SELECTED
+      });
+
+      FauxtonAPI.Events.trigger('headerbar:deleteselected');
+    },
+
+    toggleHeaderControls: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.TOGGLE_HEADER_CONTROLS
+      });
+    },
+
+    resetHeaderController: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.RESET_HEADER_BAR
+      });
+    }
+
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/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
new file mode 100644
index 0000000..180dabb
--- /dev/null
+++ b/app/addons/documents/header/header.actiontypes.js
@@ -0,0 +1,22 @@
+// 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 {
+    UPDATE_DOCUMENT_COUNT: 'UPDATE_DOCUMENT_COUNT',
+    COLLAPSE_DOCUMENTS: 'COLLAPSE_DOCUMENTS',
+    TOGGLE_HEADER_CONTROLS: 'TOGGLE_HEADER_CONTROLS',
+    RESET_HEADER_BAR: 'RESET_HEADER_BAR',
+    DELETE_SELECTED_DOCUMENTS: 'DELETE_SELECTED_DOCUMENTS'
+  };
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/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
new file mode 100644
index 0000000..faf46bc
--- /dev/null
+++ b/app/addons/documents/header/header.react.jsx
@@ -0,0 +1,245 @@
+// 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',
+  'react',
+  'addons/documents/header/header.stores',
+  'addons/documents/header/header.actions'
+],
+
+function (app, FauxtonAPI, React, Stores, Actions) {
+  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+  var headerBarStore = Stores.headerBarStore;
+  var bulkDocumentHeaderStore = Stores.bulkDocumentHeaderStore;
+
+  // this will be a global component
+  var ToggleHeaderButton = React.createClass({
+    render: function () {
+      var iconClasses = 'icon ' + this.props.fonticon + ' ' + this.props.innerClasses,
+          containerClasses = 'button ' + this.props.containerClasses;
+
+      if (this.props.setEnabledClass) {
+        containerClasses = containerClasses + ' js-headerbar-togglebutton-selected';
+      }
+
+      return (
+        <button
+          title={this.props.title}
+          disabled={this.props.disabled}
+          onClick={this.props.toggleCallback}
+          className={containerClasses}
+          >
+          <i className={iconClasses}></i><span>{this.props.text}</span>
+        </button>
+      );
+    }
+  });
+
+  var BulkDocumentHeaderController = React.createClass({
+    getStoreState: function () {
+      return {
+        areDocumentsCollapsed: bulkDocumentHeaderStore.getCollapsedState(),
+        isDeselectPossible: bulkDocumentHeaderStore.getIsDeselectPossible(),
+        isSelectAllPossible: bulkDocumentHeaderStore.getIsSelectAllPossible()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    componentDidMount: function () {
+      bulkDocumentHeaderStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function() {
+      bulkDocumentHeaderStore.off('change', this.onChange);
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    render: function () {
+      var baseClass = 'header-control-box header-control-square ',
+          isDeselectPossible = this.state.isDeselectPossible,
+          isSelectAllPossible = this.state.isSelectAllPossible,
+          areDocumentsCollapsed = this.state.areDocumentsCollapsed;
+
+      return (
+        <div className='alternative-header'>
+          <ToggleHeaderButton
+            fonticon={'fonticon-select-all'}
+            toggleCallback={this.selectAllDocuments}
+            innerClasses={''}
+            containerClasses={baseClass + 'control-select-all'}
+            text={''}
+            setEnabledClass={!isSelectAllPossible}
+            disabled={!isSelectAllPossible}
+            title={'Select all Documents'} />
+
+          <ToggleHeaderButton
+            fonticon={'fonticon-deselect-all'}
+            toggleCallback={this.deSelectAllDocuments}
+            innerClasses={''}
+            containerClasses={baseClass + 'control-de-select-all'}
+            text={''}
+            setEnabledClass={!isDeselectPossible}
+            disabled={!isDeselectPossible}
+            title={'Deselect all Documents'} />
+
+          <ToggleHeaderButton
+            fonticon={'fonticon-collapse'}
+            toggleCallback={this.toggleCollapseDocuments}
+            innerClasses={''}
+            containerClasses={baseClass + 'control-collapse'}
+            text={''}
+            setEnabledClass={areDocumentsCollapsed}
+            disabled={areDocumentsCollapsed}
+            title={'Collapse all'} />
+
+          <ToggleHeaderButton
+            fonticon={'fonticon-expand'}
+            toggleCallback={this.toggleCollapseDocuments}
+            innerClasses={''}
+            containerClasses={baseClass + 'control-expand'}
+            text={''}
+            setEnabledClass={!areDocumentsCollapsed}
+            disabled={!areDocumentsCollapsed}
+            title={'Expand all'} />
+
+          <ToggleHeaderButton
+            fonticon={'fonticon-trash'}
+            toggleCallback={this.deleteSelected}
+            innerClasses={''}
+            containerClasses={baseClass + 'control-delete'}
+            text={''}
+            title={'Delete selected'} />
+
+          <ToggleHeaderButton
+            fonticon={''}
+            toggleCallback={this.cancelView}
+            innerClasses={''}
+            containerClasses={baseClass + 'control-cancel'}
+            text={'Cancel'}
+            title={'Switch to other view'} />
+        </div>
+      );
+    },
+
+    toggleCollapseDocuments: function () {
+      Actions.toggleCollapseDocuments();
+    },
+
+    selectAllDocuments: function () {
+      Actions.toggleSelectAllDocuments(false);
+    },
+
+    deSelectAllDocuments: function () {
+      Actions.toggleSelectAllDocuments(true);
+    },
+
+    cancelView: function () {
+      Actions.toggleHeaderControls();
+    },
+
+    deleteSelected: function () {
+      Actions.deleteSelected();
+    }
+  });
+
+  var HeaderBarController = React.createClass({
+    getStoreState: function () {
+      return {
+        isToggled: headerBarStore.getToggleStatus(),
+        toggleClass: headerBarStore.getToggleClass()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    componentDidMount: function () {
+      headerBarStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function() {
+      headerBarStore.off('change', this.onChange);
+    },
+
+    toggleCallback: function () {
+      Actions.toggleHeaderControls();
+    },
+
+    componentDidUpdate: function () {
+      // todo reactify right header (api bar, query options)
+      var $oldHeader = $('#api-navbar, #right-header');
+      if (this.state.isToggled) {
+        $oldHeader.hide();
+        return;
+      }
+
+      setTimeout(function () {
+        $oldHeader.velocity('fadeIn', 250);
+      }, 250);
+    },
+
+    render: function () {
+      var containerClasses = 'header-control-box ' +
+        'control-toggle-alternative-header ' + this.state.toggleClass;
+      var innerClasses = '';
+
+      var headerbar = null;
+      if (this.state.isToggled) {
+        headerbar = (<BulkDocumentHeaderController key={1} />);
+      }
+
+      return (
+        <div>
+          <div>
+            <ToggleHeaderButton
+              fonticon={'fonticon-ok-circled'}
+              toggleCallback={this.toggleCallback}
+              containerClasses={containerClasses}
+              innerClasses={innerClasses}
+              text={'Select'} />
+              <ReactCSSTransitionGroup transitionName={'fade'}>
+                {headerbar}
+              </ReactCSSTransitionGroup>
+          </div>
+        </div>
+      );
+    }
+  });
+
+  var Views = {
+    renderHeaderController: function (el) {
+      React.render(<HeaderBarController/>, el);
+    },
+    removeHeaderController: function (el) {
+      React.unmountComponentAtNode(el);
+    },
+    BulkDocumentHeaderController: BulkDocumentHeaderController,
+    HeaderBarController: HeaderBarController,
+    ToggleHeaderButton: ToggleHeaderButton
+  };
+
+  return Views;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/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
new file mode 100644
index 0000000..c491043
--- /dev/null
+++ b/app/addons/documents/header/header.stores.js
@@ -0,0 +1,150 @@
+// 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/header/header.actiontypes'
+],
+
+function (FauxtonAPI, ActionTypes) {
+  var Stores = {};
+
+  Stores.BulkDocumentHeaderStore = FauxtonAPI.Store.extend({
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._collapsedDocuments = false;
+      this._selectedAllDocuments = false;
+
+      this._selectedDocumentsCount = 0;
+      this._documentsOnPageCount = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE;
+    },
+
+    toggleCollapse: function () {
+      this._collapsedDocuments = !this._collapsedDocuments;
+    },
+
+    getCollapsedState: function () {
+      return this._collapsedDocuments;
+    },
+
+    toggleSelectAll: function () {
+      this._selectedAllDocuments = !this._selectedAllDocuments;
+    },
+
+    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();
+    },
+
+    reset: function () {
+      this._isToggled = false;
+    },
+
+    toogleStatus: function () {
+      this._isToggled = !this._isToggled;
+    },
+
+    toggleClass: function () {
+      this._toggleClass = '';
+
+      if (this._isToggled) {
+        this._toggleClass = 'js-headerbar-togglebutton-selected';
+      }
+    },
+
+    getToggleStatus: function () {
+      return this._isToggled;
+    },
+
+    getToggleClass: function () {
+      return this._toggleClass;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.TOGGLE_HEADER_CONTROLS:
+          this.toogleStatus();
+          this.toggleClass();
+          this.triggerChange();
+        break;
+
+        case ActionTypes.RESET_HEADER_BAR:
+          this.reset();
+          this.toggleClass();
+          this.triggerChange();
+        break;
+
+        default:
+          return;
+      }
+    }
+  });
+
+  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/25dab477/app/addons/documents/routes-documents.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index c776a48..b88ee22 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -170,6 +170,8 @@ function(app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
Datab
         return;
       }
 
+      this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar());
+
       this.footer = this.setView('#footer', new Documents.Views.Footer());
 
       this.leftheader.updateCrumbs(this.getCrumbs(this.database));
@@ -519,6 +521,9 @@ function(app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
Datab
     },
 
     cleanup: function () {
+      if (this.reactHeader) {
+        this.removeView('#react-headerbar');
+      }
       if (this.viewEditor) {
         this.removeView('#dashboard-upper-content');
       }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/templates/all_docs_list.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/all_docs_list.html b/app/addons/documents/templates/all_docs_list.html
index 7f7a2ca..3a29d96 100644
--- a/app/addons/documents/templates/all_docs_list.html
+++ b/app/addons/documents/templates/all_docs_list.html
@@ -13,20 +13,6 @@
 %>
 
 <div class="view">
-  <% if (!viewList) { %>
-    <div class="row">
-      <div class="btn-toolbar span6">
-        <button type="button" class="btn btn-small js-all" data-toggle="button">✓
All</button>
-        <button class="btn btn-small disabled js-bulk-delete"><i class="icon-trash"></i></button>
-        <% if (expandDocs) { %>
-        <button id="collapse" class="btn btn-small"><i class="icon-minus"></i>
Collapse</button>
-        <% } else { %>
-        <button id="collapse" class="btn btn-small"><i class="icon-plus"></i>
Expand</button>
-        <% } %>
-      </div>
-    </div>
-  <% } %>
-
   <div id="doc-list"></div>
 
   <% if (endOfResults) { %>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/tests/headerSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/headerSpec.react.jsx b/app/addons/documents/tests/headerSpec.react.jsx
new file mode 100644
index 0000000..874cf84
--- /dev/null
+++ b/app/addons/documents/tests/headerSpec.react.jsx
@@ -0,0 +1,146 @@
+// 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/header/header.react',
+  'addons/documents/header/header.stores',
+  'addons/documents/header/header.actions',
+
+  // importing the legacy
+  'addons/documents/views',
+  'addons/documents/resources',
+  'addons/databases/base',
+  'addons/fauxton/components',
+
+  'testUtils',
+  'react'
+], function (FauxtonAPI, Views, Stores, Actions, Documents, Resources, Databases, Components,
utils, React) {
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+
+  describe('Header Togglebutton', function () {
+    var container, toggleEl, toggleCallback;
+    beforeEach(function () {
+      container = document.createElement('div');
+      toggleCallback = sinon.spy();
+      toggleEl = TestUtils.renderIntoDocument(<Views.ToggleHeaderButton fonticon={'foo'}
+        classString={'bar'} toggleCallback={toggleCallback} />, container);
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('should call the passed callback', function () {
+      TestUtils.Simulate.click(toggleEl.getDOMNode());
+      assert.ok(toggleCallback.calledOnce);
+    });
+  });
+
+  describe('Header Controller', function () {
+    var container, toggleEl;
+    beforeEach(function () {
+      container = document.createElement('div');
+      Actions.resetHeaderController();
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('should use the passed classname', function () {
+      toggleEl = TestUtils.renderIntoDocument(<Views.HeaderBarController />, container);
+      var $el = $(toggleEl.getDOMNode()).find('.control-toggle-alternative-header');
+      TestUtils.Simulate.click($el[0]);
+      assert.ok($el.hasClass('js-headerbar-togglebutton-selected'));
+    });
+
+    it('should not render the alternative header if the button is not clicked', function
() {
+      toggleEl = TestUtils.renderIntoDocument(<Views.HeaderBarController />, container);
+      var $el = $(toggleEl.getDOMNode()).find('.control-toggle-alternative-header');
+      assert.equal($(toggleEl.getDOMNode()).find('.alternative-header').length, 0);
+    });
+
+    it('should render the alternative header', function () {
+      toggleEl = TestUtils.renderIntoDocument(<Views.HeaderBarController />, container);
+      var $el = $(toggleEl.getDOMNode()).find('.control-toggle-alternative-header');
+      TestUtils.Simulate.click($el[0]);
+      assert.equal($(toggleEl.getDOMNode()).find('.alternative-header').length, 1);
+    });
+  });
+
+  describe('Bulkdocument Headerbar Controller', function () {
+    var container, header, viewSandbox, bulkDeleteDocCollection;
+    beforeEach(function () {
+      // needed for "pressing SelectAll should fill the delete-bulk-docs-collection"
+      var ViewSandbox = utils.ViewSandbox;
+      var database = new Databases.Model({id: 'registry'});
+      bulkDeleteDocCollection = new Resources.BulkDeleteDocCollection([], {databaseId: 'registry'});
+
+
+      database.allDocs = new Resources.AllDocs({_id: "ente"}, {
+        database: database,
+        viewMeta: {update_seq: 1},
+        params: {}
+      });
+
+      var pagination = new Components.IndexPagination({
+        collection: database.allDocs,
+        scrollToSelector: '#dashboard-content',
+        docLimit: 20,
+        perPage: 20
+      });
+
+      var allDocsNumber = new Documents.Views.AllDocsNumber({
+        collection: database.allDocs,
+        pagination: pagination,
+        perPageDefault: 20
+      });
+
+      var view = new Documents.Views.AllDocsList({
+        viewList: false,
+        bulkDeleteDocsCollection: bulkDeleteDocCollection,
+        collection: database.allDocs,
+        pagination: pagination,
+        allDocsNumber: allDocsNumber
+      });
+
+      viewSandbox = new ViewSandbox();
+      viewSandbox.renderView(view);
+
+
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      bulkDeleteDocCollection.off();
+      viewSandbox.remove();
+
+      React.unmountComponentAtNode(container);
+    });
+
+    it('should trigger an event to communicate with the backbone elements', function () {
+      var spy = sinon.spy(FauxtonAPI.Events, 'trigger');
+      header = TestUtils.renderIntoDocument(<Views.BulkDocumentHeaderController />,
container);
+      TestUtils.Simulate.click($(header.getDOMNode()).find('.control-collapse')[0]);
+
+      assert.ok(spy.calledOnce);
+    });
+
+    it('pressing SelectAll should fill the delete-bulk-docs-collection', function () {
+      TestUtils.Simulate.click($(header.getDOMNode()).find('.control-select-all')[0]);
+
+      assert.equal(bulkDeleteDocCollection.length, 1);
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/tests/nightwatch/bulkDelete.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/nightwatch/bulkDelete.js b/app/addons/documents/tests/nightwatch/bulkDelete.js
index 6cb3894..aeb1651 100644
--- a/app/addons/documents/tests/nightwatch/bulkDelete.js
+++ b/app/addons/documents/tests/nightwatch/bulkDelete.js
@@ -23,9 +23,11 @@ module.exports = {
       .createDocument(newDocumentName1, newDatabaseName)
       .createDocument(newDocumentName2, newDatabaseName)
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
-      .waitForElementPresent('.js-all', waitTime, false)
-      .click('.js-all')
-      .click('.js-bulk-delete')
+      .waitForElementPresent('.control-toggle-alternative-header', waitTime, false)
+      .click('.control-toggle-alternative-header')
+      .waitForElementPresent('.control-select-all', waitTime, false)
+      .click('.control-select-all')
+      .click('.control-delete')
       .acceptAlert()
       .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
       .waitForElementNotPresent('[data-id="' + newDocumentName1 + '"]', waitTime, false)
@@ -50,12 +52,14 @@ module.exports = {
       .loginToGUI()
       .createManyDocuments(25, newDatabaseName)
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
-      .waitForElementVisible('.js-all', waitTime, false)
-      .click('.js-all')
+      .waitForElementPresent('.control-toggle-alternative-header', waitTime, false)
+      .click('.control-toggle-alternative-header')
+      .waitForElementPresent('.control-select-all', waitTime, false)
+      .click('.control-select-all')
       .click('#next')
       .waitForElementVisible('[data-id="27"]', waitTime, false)
       .click('#previous')
-      .waitForElementPresent('.js-all.active', waitTime, false)
+      .waitForElementPresent('.control-select-all.js-headerbar-togglebutton-selected', waitTime,
false)
       .end();
   }
 };

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/tests/nightwatch/deletesDocument.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/nightwatch/deletesDocument.js b/app/addons/documents/tests/nightwatch/deletesDocument.js
index 47b6b94..744933e 100644
--- a/app/addons/documents/tests/nightwatch/deletesDocument.js
+++ b/app/addons/documents/tests/nightwatch/deletesDocument.js
@@ -22,11 +22,12 @@ module.exports = {
       .createDocument(newDocumentName, newDatabaseName)
       .url(baseUrl)
       .waitForElementPresent('#dashboard-content a[href="#/database/' + newDatabaseName +
'/_all_docs"]', waitTime, false)
-      .click('#dashboard-content a[href="#/database/'+newDatabaseName+'/_all_docs"]')
+      .click('#dashboard-content a[href="#/database/' + newDatabaseName + '/_all_docs"]')
       .waitForElementVisible('label[for="checkbox-' + newDocumentName + '"]', waitTime, false)
       .click('label[for="checkbox-' + newDocumentName + '"]')
-      .waitForElementPresent('.js-bulk-delete:not(.disabled)', waitTime, false)
-      .click('button.js-bulk-delete')
+      .click('.control-toggle-alternative-header')
+      .waitForElementPresent('.control-select-all', waitTime, false)
+      .click('.control-delete')
       .acceptAlert()
       .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
       .url(baseUrl+'/'+newDatabaseName+'/_all_docs')

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/tests/viewsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/viewsSpec.js b/app/addons/documents/tests/viewsSpec.js
index 5e416bb..2114ed8 100644
--- a/app/addons/documents/tests/viewsSpec.js
+++ b/app/addons/documents/tests/viewsSpec.js
@@ -20,54 +20,9 @@ define([
       ViewSandbox = testUtils.ViewSandbox,
       viewSandbox;
 
-      /* describe('AllDocsList', function () {
-    var database = new Databases.Model({id: 'registry'}),
-        bulkDeleteDocCollection = new Resources.BulkDeleteDocCollection([], {databaseId:
'registry'});
-
-    database.allDocs = new Resources.AllDocs({_id: "ente"}, {
-      database: database,
-      viewMeta: {update_seq: 1},
-      params: {}
-    });
-
-    var pagination = new Components.IndexPagination({
-      collection: database.allDocs,
-      scrollToSelector: '#dashboard-content',
-      docLimit: 20,
-      perPage: 20
-    });
-
-    var allDocsNumber = new Documents.Views.AllDocsNumber({
-      collection: database.allDocs,
-      pagination: pagination,
-      perPageDefault: 20
-    });
-
-    var view = new Documents.Views.AllDocsList({
-      viewList: false,
-      bulkDeleteDocsCollection: bulkDeleteDocCollection,
-      collection: database.allDocs,
-      pagination: pagination,
-      allDocsNumber: allDocsNumber
-    });
-
-    beforeEach(function (done) {
-      viewSandbox = new ViewSandbox();
-      viewSandbox.renderView(view, done);
-    });
-
-    afterEach(function () {
-      viewSandbox.remove();
-    });
-
+  describe('AllDocsList', function () {
     it('should load', function () {
       assert.equal(typeof Documents.Views.AllDocsList, 'function');
     });
-
-    it('pressing SelectAll should fill the delete-bulk-docs-collection', function () {
-      assert.equal(bulkDeleteDocCollection.length, 0);
-      view.$('button.js-all').trigger('click');
-      assert.equal(bulkDeleteDocCollection.length, 1);
-    });
-  });*/
+  });
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
index 1e3d039..5f0a1b7 100644
--- a/app/addons/documents/views.js
+++ b/app/addons/documents/views.js
@@ -21,11 +21,16 @@ define([
   "addons/documents/shared-views",
   "addons/documents/views-queryoptions",
 
+  // React
+  'addons/documents/header/header.react',
+  'addons/documents/header/header.actions',
+
   //plugins
   "plugins/prettify"
 ],
 
-function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions) {
+function (app, FauxtonAPI, Components, Documents,
+  Databases, Views, QueryOptions, ReactHeader, ReactHeaderActions) {
 
   function showError (msg) {
     FauxtonAPI.addNotification({
@@ -39,6 +44,21 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
     template: "addons/documents/templates/all_docs_footer"
   });
 
+  Views.ReactHeaderbar = FauxtonAPI.View.extend({
+    afterRender: function () {
+      ReactHeader.renderHeaderController(this.el);
+    },
+
+    cleanup: function () {
+      this.disableHeader();
+      ReactHeader.removeHeaderController(this.el);
+    },
+
+    disableHeader: function () {
+      ReactHeaderActions.resetHeaderController();
+    }
+  });
+
   Views.RightAllDocsHeader = FauxtonAPI.View.extend({
     className: "header-right",
     template: "addons/documents/templates/all_docs_header",
@@ -299,15 +319,13 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
     },
 
     events: {
-      'click button.js-all': 'selectAll',
-      "click button.js-bulk-delete": "bulkDelete",
-      "click #collapse": "collapse",
       'change input': 'toggleDocument',
       "click #js-end-results": "openQueryOptionsTray"
     },
 
     initialize: function (options) {
       this.rows = {};
+
       this.viewList = !!options.viewList;
 
       if (options.ddocInfo) {
@@ -335,6 +353,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
           msg: 'Successfully deleted your docs',
           clear:  true
         });
+
       }.bind(this));
     },
 
@@ -378,28 +397,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
       }
 
       $row.toggleClass('js-to-delete');
-      this.toggleTrash();
-    },
-
-    toggleTrash: function () {
-      var $bulkdDeleteButton = this.$('.js-bulk-delete');
-
-      if (this.bulkDeleteDocsCollection && this.bulkDeleteDocsCollection.length >
0) {
-        $bulkdDeleteButton.removeClass('disabled');
-      } else {
-        $bulkdDeleteButton.addClass('disabled');
-      }
-    },
-
-    maybeHighlightAllButton: function () {
-      if (this.$('.js-to-delete').length < this.$('.all-docs-item').length) {
-        return;
-      }
 
-      if (!this.bulkDeleteDocsCollection || this.bulkDeleteDocsCollection.length === 0) {
-        return;
-      }
-      this.$('.js-all').addClass('active');
+      ReactHeaderActions.updateDocumentCount({
+        selectedOnPage: this.$('.js-to-delete').length,
+        documentsOnPageCount: this.perPage()
+      });
     },
 
     openQueryOptionsTray: function(e) {
@@ -428,11 +430,14 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
       });
     },
 
-    selectAll: function (evt) {
+    toggleSelectAll: function (on) {
+      this.selectAllBasedOnBoolean(on);
+    },
+
+    selectAllBasedOnBoolean: function (isActive) {
       var $allDocs = this.$('#doc-list'),
           $rows = $allDocs.find('.all-docs-item'),
           $checkboxes = $rows.find('input:checkbox'),
-          isActive = $(evt.target).hasClass('active'),
           modelsAffected,
           docs;
 
@@ -440,7 +445,15 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
       $rows.toggleClass('js-to-delete', !isActive);
 
       if (isActive) {
-        this.bulkDeleteDocsCollection.reset();
+        modelsAffected = _.reduce($rows, function (acc, el) {
+          var docId = $(el).attr('data-id'),
+              model = this.collection.get(docId);
+
+          acc.push(model);
+          return acc;
+        }, [], this);
+
+        this.bulkDeleteDocsCollection.remove(modelsAffected);
       } else {
         modelsAffected = _.reduce($rows, function (acc, el) {
           var docId = $(el).attr('data-id'),
@@ -452,20 +465,19 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
         this.bulkDeleteDocsCollection.add(modelsAffected);
       }
 
-      this.toggleTrash();
+      ReactHeaderActions.updateDocumentCount({
+        selectedOnPage: this.$('.js-to-delete').length,
+        documentsOnPageCount: this.perPage()
+      });
     },
 
     serialize: function() {
       return {
-        viewList: this.viewList,
-        expandDocs: this.expandDocs,
         endOfResults: !this.pagination.canShowNextfn()
       };
     },
 
-    collapse: function (event) {
-      event.preventDefault();
-
+    collapse: function () {
       if (this.expandDocs) {
         this.expandDocs = false;
       } else {
@@ -555,15 +567,21 @@ function(app, FauxtonAPI, Components, Documents, Databases, Views, QueryOptions)
 
       prettyPrint();
 
+      this.stopListening(FauxtonAPI.Events);
+      this.listenTo(FauxtonAPI.Events, 'headerbar:collapse', this.collapse);
+      this.listenTo(FauxtonAPI.Events, 'headerbar:selectall', this.toggleSelectAll);
+      this.listenTo(FauxtonAPI.Events, 'headerbar:deleteselected', this.bulkDelete);
+
       if (this.bulkDeleteDocsCollection) {
         this.stopListening(this.bulkDeleteDocsCollection);
         this.listenTo(this.bulkDeleteDocsCollection, 'error', this.showError);
         this.listenTo(this.bulkDeleteDocsCollection, 'removed', this.removeDocuments);
-        this.listenTo(this.bulkDeleteDocsCollection, 'updated', this.toggleTrash);
       }
 
-      this.toggleTrash();
-      this.maybeHighlightAllButton();
+      ReactHeaderActions.updateDocumentCount({
+        selectedOnPage: this.$('.js-to-delete').length,
+        documentsOnPageCount: this.perPage()
+      });
     },
 
     perPage: function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/app.js
----------------------------------------------------------------------
diff --git a/app/app.js b/app/app.js
index 9064b57..382fd99 100644
--- a/app/app.js
+++ b/app/app.js
@@ -43,7 +43,6 @@ function(app, $, _, Backbone, Bootstrap, Helpers, constants, Utils, FauxtonAPI,
     };
   }
 
-
   // Provide a global location to place configuration settings and module
   // creation also mix in Backbone.Events
   _.extend(app, {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/constants.js
----------------------------------------------------------------------
diff --git a/app/constants.js b/app/constants.js
index 7662b5b..5cef015 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -24,7 +24,7 @@ define([], function () {
       DOCUMENT_LIMIT: 100
     },
 
-    // events
+    // global events for common used components
     EVENTS: {
       TRAY_CLOSED: 'tray:closed',
       TRAY_OPENED: 'tray:opened',

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/app/templates/layouts/with_tabs_sidebar.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/with_tabs_sidebar.html b/app/templates/layouts/with_tabs_sidebar.html
index fd4aaa0..e6acfa3 100644
--- a/app/templates/layouts/with_tabs_sidebar.html
+++ b/app/templates/layouts/with_tabs_sidebar.html
@@ -18,6 +18,7 @@ the License.
     <div class="header-wrapper">
       <div id="breadcrumbs" class="sidebar"></div>
       <div class="right-header-wrapper">
+        <div id="react-headerbar"></div>
         <div id="api-navbar"></div>
         <div id="right-header"></div>
       </div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/assets/less/animations.less
----------------------------------------------------------------------
diff --git a/assets/less/animations.less b/assets/less/animations.less
index 8de09e7..cca377d 100644
--- a/assets/less/animations.less
+++ b/assets/less/animations.less
@@ -10,3 +10,17 @@
   animation: @options;
 }
 
+.fade {
+  transition: opacity .25s ease-in-out;
+  -moz-transition: opacity .25s ease-in-out;
+  -webkit-transition: opacity .25s ease-in-out;
+}
+
+
+.fadeIn {
+  opacity: 1;
+}
+
+.fadeOut {
+  opacity: 0;
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/assets/less/fauxton.less
----------------------------------------------------------------------
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index 36259e7..3eead5d 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -28,7 +28,7 @@
 @import "trays.less";
 @import "mixins.less";
 @import "animations.less";
-
+@import "react-animations.less";
 
 /**
  * HTML-wide overrides

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25dab477/assets/less/react-animations.less
----------------------------------------------------------------------
diff --git a/assets/less/react-animations.less b/assets/less/react-animations.less
new file mode 100644
index 0000000..fbdbc44
--- /dev/null
+++ b/assets/less/react-animations.less
@@ -0,0 +1,17 @@
+.fade-enter {
+  opacity: 0;
+  transition: opacity .25s;
+
+  &.fade-enter-active {
+    opacity: 1;
+  }
+}
+
+.fade-leave {
+  opacity: 1;
+  transition: opacity .25s;
+
+  &.fade-leave-active {
+    opacity: 0;
+  }
+}


Mime
View raw message