You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2016/02/23 14:04:33 UTC

fauxton commit: updated refs/heads/master to 40d956f

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master fb03640e5 -> 40d956f07


trays: use higher order components to connect them to stores

this is the first step to get rid of <Tray /> which uses
`FauxtonAPI.Events` and additionally  manages its own state by
accessing the DOM which leads to subtle bugs.

we use composition/higher order components as all trays share the
same wrapper, but have different stores connected.

 - change test urls to example.com, which is reserved for testing
 - use React.findDOMNode
 - trays don't touch the DOM directly any more

COUCHDB-2943

PR: #642
PR-URL: https://github.com/apache/couchdb-fauxton/pull/642
Reviewed-By: Benjamin Keen <be...@gmail.com>


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

Branch: refs/heads/master
Commit: 40d956f0787087f47332e11e40f4b65cc9d0ce4a
Parents: fb03640
Author: Robert Kowalski <ro...@apache.org>
Authored: Tue Feb 9 17:39:55 2016 +0000
Committer: Robert Kowalski <ro...@apache.org>
Committed: Tue Feb 23 13:04:20 2016 +0000

----------------------------------------------------------------------
 app/addons/components/actions.js                |  33 ++-
 app/addons/components/actiontypes.js            |   9 +-
 .../components/react-components.react.jsx       | 288 +++++++++++++------
 app/addons/components/stores.js                 |  55 ++--
 .../tests/apiBarControllerSpec.react.jsx        | 101 +++++--
 app/addons/components/tests/storesSpec.js       |  68 -----
 app/addons/fauxton/base.js                      |  11 +-
 assets/less/trays.less                          |   2 +-
 8 files changed, 333 insertions(+), 234 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/app/addons/components/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/components/actions.js b/app/addons/components/actions.js
index ca12b5d..e31f7e3 100644
--- a/app/addons/components/actions.js
+++ b/app/addons/components/actions.js
@@ -16,29 +16,31 @@ define([
 ],
 function (FauxtonAPI, ActionTypes) {
 
-  function showAPIBar () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SHOW_API_BAR });
+  function showAPIBarButton () {
+    FauxtonAPI.dispatch({ type: ActionTypes.CMPNTS_SHOW_API_BAR_BUTTON });
   }
 
-  function hideAPIBar () {
-    FauxtonAPI.dispatch({ type: ActionTypes.HIDE_API_BAR });
+  function hideAPIBarButton () {
+    FauxtonAPI.dispatch({ type: ActionTypes.CMPNTS_HIDE_API_BAR_BUTTON });
+  }
+
+  function toggleApiBarVisibility (visible) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.CMPNTS_SET_API_BAR_CONTENT_VISIBLE_STATE,
+      options: visible
+    });
   }
 
-  // general usage for setting multiple params at once. If a param isn't passed, it's not overridden
   function updateAPIBar (params) {
     FauxtonAPI.dispatch({
-      type: ActionTypes.UPDATE_API_BAR,
-      options: {
-        visible: params.visible,
-        endpoint: params.endpoint,
-        docURL: params.docURL
-      }
+      type: ActionTypes.CMPNTS_UPDATE_API_BAR,
+      options: params
     });
   }
 
   function showDeleteDatabaseModal (options) {
     FauxtonAPI.dispatch({
-      type: ActionTypes.COMPONENTS_DATABASES_SHOWDELETE_MODAL,
+      type: ActionTypes.CMPNTS_DATABASES_SHOWDELETE_MODAL,
       options: options
     });
   }
@@ -73,9 +75,10 @@ function (FauxtonAPI, ActionTypes) {
   return {
     deleteDatabase: deleteDatabase,
     showDeleteDatabaseModal: showDeleteDatabaseModal,
-    showAPIBar: showAPIBar,
-    hideAPIBar: hideAPIBar,
-    updateAPIBar: updateAPIBar
+    showAPIBarButton: showAPIBarButton,
+    hideAPIBarButton: hideAPIBarButton,
+    toggleApiBarVisibility: toggleApiBarVisibility,
+    updateAPIBar: updateAPIBar,
   };
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/app/addons/components/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/components/actiontypes.js b/app/addons/components/actiontypes.js
index 28e608d..454fe28 100644
--- a/app/addons/components/actiontypes.js
+++ b/app/addons/components/actiontypes.js
@@ -13,10 +13,11 @@
 define([],  function () {
 
   return {
-    SHOW_API_BAR: 'SHOW_API_BAR',
-    HIDE_API_BAR: 'HIDE_API_BAR',
-    UPDATE_API_BAR: 'UPDATE_API_BAR',
-    COMPONENTS_DATABASES_SHOWDELETE_MODAL: 'COMPONENTS_DATABASES_SHOWDELETE_MODAL'
+    CMPNTS_SHOW_API_BAR_BUTTON: 'CMPNTS_SHOW_API_BAR_BUTTON',
+    CMPNTS_HIDE_API_BAR_BUTTON: 'CMPNTS_HIDE_API_BAR_BUTTON',
+    CMPNTS_UPDATE_API_BAR: 'CMPNTS_UPDATE_API_BAR',
+    CMPNTS_SET_API_BAR_CONTENT_VISIBLE_STATE: 'CMPNTS_SET_API_BAR_CONTENT_VISIBLE_STATE',
+    CMPNTS_DATABASES_SHOWDELETE_MODAL: 'CMPNTS_DATABASES_SHOWDELETE_MODAL'
   };
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/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 c5e19cd..356197f 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -15,8 +15,8 @@ define([
   'api',
   'react',
   'react-dom',
-  'addons/components/stores',
   'addons/components/actions',
+  'addons/components/stores',
 
   'addons/fauxton/components.react',
   'addons/documents/helpers',
@@ -25,7 +25,7 @@ define([
   'libs/react-bootstrap'
 ],
 
-function (app, FauxtonAPI, React, ReactDOM, Stores, Actions,
+function (app, FauxtonAPI, React, ReactDOM, Actions, Stores,
   FauxtonComponents, Helpers, ace, beautifyHelper, ReactBootstrap) {
 
   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@@ -1185,6 +1185,7 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions,
 
   var TrayContents = React.createClass({
     getChildren: function () {
+      // XXX sofmigration new tray wrapper
       if (!this.props.trayVisible) {
         return null;
       }
@@ -1206,6 +1207,197 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions,
     }
   });
 
+
+  function connectToStores (Component, stores, getStateFromStores) {
+
+    var WrappingElement = React.createClass({
+
+      componentDidMount: function () {
+        stores.forEach(function (store) {
+          store.on('change', this.onChange, this);
+        }.bind(this));
+      },
+
+      componentWillUnmount: function () {
+        stores.forEach(function (store) {
+          store.off('change', this.onChange);
+        }.bind(this));
+      },
+
+      getInitialState: function () {
+        return getStateFromStores(this.props);
+      },
+
+      onChange: function () {
+        if (!this.isMounted()) {
+          return;
+        }
+
+        this.setState(getStateFromStores(this.props));
+      },
+
+      handleStoresChanged: function () {
+        if (this.isMounted()) {
+          this.setState(getStateFromStores(this.props));
+        }
+      },
+
+      render: function () {
+        return <Component {...this.state} {...this.props} />;
+      }
+
+    });
+
+    return WrappingElement;
+  }
+
+  var TrayWrapper = React.createClass({
+    getDefaultProps: function () {
+      return {
+        className: ''
+      };
+    },
+
+    renderChildren: function () {
+      return React.Children.map(this.props.children, function (child, key) {
+        return React.cloneElement(child, this.props);
+      }.bind(this));
+    },
+
+    render: function () {
+      return (
+        <div>
+          {this.renderChildren()}
+        </div>
+      );
+    }
+  });
+
+  var APIBar = React.createClass({
+    propTypes: {
+      buttonVisible: React.PropTypes.bool.isRequired,
+      contentVisible: React.PropTypes.bool.isRequired,
+      docURL: React.PropTypes.string,
+      endpoint: React.PropTypes.string
+    },
+
+    showCopiedMessage: function () {
+      FauxtonAPI.addNotification({
+        msg: 'The API URL has been copied to the clipboard.',
+        type: 'success',
+        clear: true
+      });
+    },
+
+    getDocIcon: function () {
+      if (!this.props.docURL) {
+        return null;
+      }
+      return (
+        <a
+          className="help-link"
+          data-bypass="true"
+          href={this.props.docURL}
+          target="_blank"
+        >
+          <i className="icon icon-question-sign"></i>
+        </a>
+      );
+    },
+
+    getTray: function () {
+      if (!this.props.contentVisible) {
+        return null;
+      }
+
+      return (
+        // XXX softmigration new tray wrapper: trayVisible={true}
+        <TrayContents trayVisible={true} className="tray show-tray api-bar-tray">
+          <div className="input-prepend input-append">
+            <span className="add-on">
+              API URL
+              {this.getDocIcon()}
+            </span>
+
+            <FauxtonComponents.ClipboardWithTextField
+              onClipBoardClick={this.showCopiedMessage}
+              text="Copy"
+              textToCopy={this.props.endpoint}
+              uniqueKey="clipboard-apiurl" />
+
+            <div className="add-on">
+              <a
+                data-bypass="true"
+                href={this.props.endpoint}
+                target="_blank"
+                className="btn"
+              >
+                <i className="fonticon-eye icon"></i>
+                View JSON
+              </a>
+            </div>
+          </div>
+        </TrayContents>
+      );
+    },
+
+    toggleTrayVisibility: function () {
+      Actions.toggleApiBarVisibility(!this.props.contentVisible);
+    },
+
+    componentDidMount: function () {
+      $('body').on('click.APIBar', function () {
+        Actions.toggleApiBarVisibility(false);
+      }.bind(this));
+    },
+
+    componentWillUnmount: function () {
+      $('body').off('click.APIBar');
+    },
+
+    render: function () {
+      if (!this.props.buttonVisible || !this.props.endpoint) {
+        return null;
+      }
+
+      return (
+        <div>
+          <ToggleHeaderButton
+            containerClasses="header-control-box control-toggle-api-url"
+            title="API URL"
+            fonticon="fonticon-link"
+            text="API"
+            toggleCallback={this.toggleTrayVisibility} />
+
+          {this.getTray()}
+        </div>
+      );
+    }
+  });
+
+  var ApiBarController = React.createClass({
+
+    getWrap: function () {
+      return connectToStores(TrayWrapper, [componentStore], function () {
+        return {
+          buttonVisible: componentStore.getIsAPIBarButtonVisible(),
+          contentVisible: componentStore.getIsAPIBarVisible(),
+          endpoint: componentStore.getEndpoint(),
+          docURL: componentStore.getDocURL()
+        };
+      });
+    },
+
+    render: function () {
+      var TrayWrapper = this.getWrap();
+      return (
+        <TrayWrapper>
+          <APIBar buttonVisible={true} contentVisible={false} />
+        </TrayWrapper>
+      );
+    }
+  });
+
   // The tray components work as follows:
   // <Tray> Outer wrapper for all components in the tray
   // <ToggleHeaderButton /> The tray button to activate the tray, e.g the ToggleHeaderButton
@@ -1279,96 +1471,6 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions,
 
   });
 
-
-  var ApiBarController = React.createClass({
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    getStoreState: function () {
-      return {
-        visible: componentStore.isAPIBarVisible(),
-        endpoint: componentStore.getEndpoint(),
-        docURL: componentStore.getDocURL()
-      };
-    },
-
-    onChange: function () {
-      if (this.isMounted()) {
-        this.setState(this.getStoreState());
-      }
-    },
-
-    componentDidMount: function () {
-      componentStore.on('change', this.onChange, this);
-    },
-
-    componentWillUnmount: function () {
-      componentStore.off('change', this.onChange);
-    },
-
-    showCopiedMessage: function () {
-      FauxtonAPI.addNotification({
-        msg: 'The API URL has been copied to the clipboard.',
-        type: 'success',
-        clear: true
-      });
-    },
-
-    getDocIcon: function () {
-      if (!this.state.docURL) {
-        return false;
-      }
-      return (
-        <a className="help-link" data-bypass="true" href={this.state.docURL} target="_blank">
-          <i className="icon icon-question-sign"></i>
-        </a>
-      );
-    },
-
-    render: function () {
-      if (!this.state.visible || !this.state.endpoint) {
-        return null;
-      }
-
-      return (
-        <Tray id="api-bar-controller" ref="tray">
-
-          <ToggleHeaderButton
-            containerClasses="header-control-box control-toggle-api-url"
-            title="API URL"
-            fonticon="fonticon-link"
-            text="API" />
-
-          <TrayContents
-            className="api-bar-tray">
-            <div className="input-prepend input-append">
-              <span className="add-on">
-                API URL
-                {this.getDocIcon()}
-              </span>
-
-              <FauxtonComponents.ClipboardWithTextField
-                onClipBoardClick={this.showCopiedMessage}
-                text="Copy"
-                textToCopy={this.state.endpoint}
-                uniqueKey="clipboard-apiurl" />
-
-              <div className="add-on">
-                <a data-bypass="true" href={this.state.endpoint} target="_blank" className="btn">
-                  <i className="fonticon-eye icon"></i>
-                  View JSON
-                </a>
-              </div>
-            </div>
-
-          </TrayContents>
-        </Tray>
-      );
-    }
-  });
-
   var DeleteDatabaseModal = React.createClass({
 
     getInitialState: function () {
@@ -1482,6 +1584,8 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions,
     MenuDropDown: MenuDropDown,
     Tray: Tray,
     TrayContents: TrayContents,
+    TrayWrapper: TrayWrapper,
+    connectToStores: connectToStores,
     ApiBarController: ApiBarController,
     renderMenuDropDown: function (el, opts) {
       ReactDOM.render(<MenuDropDown icon="fonticon-cog" links={opts.links} />, el);

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/app/addons/components/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/components/stores.js b/app/addons/components/stores.js
index eccb002..6da96af 100644
--- a/app/addons/components/stores.js
+++ b/app/addons/components/stores.js
@@ -26,25 +26,25 @@ function (FauxtonAPI, app, ActionTypes) {
     },
 
     reset: function () {
-      this._apiBarVisible = true;
+      this._apiBarVisible = false;
+      this._apiBarButtonVisible = true;
       this._endpoint = '';
       this._docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
     },
 
-    isAPIBarVisible: function () {
-      return this._apiBarVisible;
+    updateAPIBar: function (settings) {
+      this._apiBarVisible = settings.contentVisible;
+      this._apiBarButtonVisible = settings.buttonVisible;
+      this._endpoint = settings.endpoint;
+      this._docURL = settings.docURL;
     },
 
-    updateAPIBar: function (settings) {
-      if (!_.isUndefined(settings.visible)) {
-        this._apiBarVisible = settings.visible;
-      }
-      if (!_.isUndefined(settings.endpoint)) {
-        this._endpoint = settings.endpoint;
-      }
-      if (!_.isUndefined(settings.docURL)) {
-        this._docURL = settings.docURL;
-      }
+    setVisibleButton: function (state) {
+      this._apiBarButtonVisible = state;
+    },
+
+    setApiBarVisible: function (state) {
+      this._apiBarVisible = state;
     },
 
     getEndpoint: function () {
@@ -55,27 +55,38 @@ function (FauxtonAPI, app, ActionTypes) {
       return this._docURL;
     },
 
+    getIsAPIBarButtonVisible: function () {
+      return this._apiBarButtonVisible;
+    },
+
+    getIsAPIBarVisible: function () {
+      return this._apiBarVisible;
+    },
+
     dispatch: function (action) {
       switch (action.type) {
-        case ActionTypes.SHOW_API_BAR:
-          this._apiBarVisible = true;
-          this.triggerChange();
+        case ActionTypes.CMPNTS_SHOW_API_BAR_BUTTON:
+          this.setVisibleButton(true);
         break;
 
-        case ActionTypes.HIDE_API_BAR:
-          this._apiBarVisible = false;
-          this.triggerChange();
+        case ActionTypes.CMPNTS_HIDE_API_BAR_BUTTON:
+          this.setVisibleButton(false);
         break;
 
-        case ActionTypes.UPDATE_API_BAR:
+        case ActionTypes.CMPNTS_SET_API_BAR_CONTENT_VISIBLE_STATE:
+          this.setApiBarVisible(action.options);
+        break;
+
+        case ActionTypes.CMPNTS_UPDATE_API_BAR:
           this.updateAPIBar(action.options);
-          this.triggerChange();
         break;
 
         default:
         return;
           // do nothing
       }
+
+      this.triggerChange();
     }
   });
 
@@ -99,7 +110,7 @@ function (FauxtonAPI, app, ActionTypes) {
 
     dispatch: function (action) {
       switch (action.type) {
-        case ActionTypes.COMPONENTS_DATABASES_SHOWDELETE_MODAL:
+        case ActionTypes.CMPNTS_DATABASES_SHOWDELETE_MODAL:
           this.setDeleteModal(action.options);
         break;
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/app/addons/components/tests/apiBarControllerSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/tests/apiBarControllerSpec.react.jsx b/app/addons/components/tests/apiBarControllerSpec.react.jsx
index 4ca4b5d..52ae2f4 100644
--- a/app/addons/components/tests/apiBarControllerSpec.react.jsx
+++ b/app/addons/components/tests/apiBarControllerSpec.react.jsx
@@ -40,49 +40,61 @@ define([
     it('Doesn\'t show up when explicitly set to visible false', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
       Actions.updateAPIBar({
-        visible: false,
-        endpoint: 'http://link.com',
-        docURL: 'http://link.com'
+        buttonVisible: false,
+        endpoint: 'http://link.example.com',
+        docURL: 'http://link.example.com',
+        contentVisible: false
       });
-      assert.equal(el.getDOMNode(), null);
+      assert.equal($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url').length, 0);
     });
 
     it('Shows up when set to visible', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
       Actions.updateAPIBar({
-        visible: true,
-        endpoint: 'http://link.com',
-        docURL: 'http://link.com'
+        buttonVisible: true,
+        endpoint: 'http://link.example.com',
+        docURL: 'http://link.example.com',
+        contentVisible: false
       });
-      assert.notEqual(el.getDOMNode(), null);
+      assert.equal($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url').length, 1);
     });
 
     it('Doesn\'t show up when set to visible BUT there\'s no endpoint defined', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
       Actions.updateAPIBar({
-        visible: true,
+        buttonVisible: true,
         endpoint: '',
-        docURL: 'http://link.com'
+        docURL: 'http://link.example.com',
+        contentVisible: false
       });
-      assert.equal(el.getDOMNode(), null);
+      assert.equal($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url').length, 0);
     });
 
     it('Confirm hide/show actions update component', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
 
-      // set an initial value
-      Actions.updateAPIBar({ endpoint: 'http://link.com' });
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: 'http://rocko.example.com',
+        docURL: 'http://link.example.com',
+        contentVisible: false
+      });
 
-      Actions.showAPIBar();
-      assert.notEqual(el.getDOMNode(), null);
+      Actions.showAPIBarButton();
+      assert.equal($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url').length, 1, 'showAPIBarButton');
 
-      Actions.hideAPIBar();
-      assert.equal(el.getDOMNode(), null);
+      Actions.hideAPIBarButton();
+      assert.equal($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url').length, 0, 'hideAPIBarButton');
     });
 
     it('Confirm doc link icon appears when docURL set', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
-      Actions.updateAPIBar({ visible: true, endpoint: 'http://link.com', docURL: 'http://doc.com' });
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: 'http://rocko.example.com',
+        docURL: 'http://doc.example.com',
+        contentVisible: false
+      });
 
       TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url')[0]);
       assert.equal($(ReactDOM.findDOMNode(el)).find('.help-link').length, 1);
@@ -90,7 +102,12 @@ define([
 
     it('Confirm doc link icon doesn\'t appear with no docURL', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
-      Actions.updateAPIBar({ visible: true, endpoint: 'http://link.com', docURL: null });
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: 'http://rocko.example.com',
+        docURL: null,
+        contentVisible: false
+      });
 
       TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url')[0]);
       assert.equal($(ReactDOM.findDOMNode(el)).find('.help-link').length, 0);
@@ -98,8 +115,13 @@ define([
 
     it('Confirm endpoint appears in markup', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
-      var link = 'http://booyah.ca';
-      Actions.updateAPIBar({ visible: true, endpoint: link, docURL: null });
+      var link = 'http://booyah.example.com';
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: link,
+        docURL: null,
+        contentVisible: false
+      });
 
       TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url')[0]);
       assert.equal($(ReactDOM.findDOMNode(el)).find('.text-field-to-copy').val(), link);
@@ -107,27 +129,48 @@ define([
 
     it('Confirm endpoint is updated in markup', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
-      var link = 'http://booyah.ca';
-      Actions.updateAPIBar({ visible: true, endpoint: link, docURL: null });
+      var link = 'http://booyah.example.com';
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: link,
+        docURL: null,
+        contentVisible: false
+      });
 
       TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url')[0]);
       assert.equal($(ReactDOM.findDOMNode(el)).find('.text-field-to-copy').val(), link);
 
-      var newLink = 'http://chickensarenoisy.com';
-      Actions.updateAPIBar({ endpoint: newLink });
+      var newLink = 'http://chickensarenoisy.example.com';
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: newLink,
+        docURL: null,
+        contentVisible: true
+      });
+
       assert.equal($(ReactDOM.findDOMNode(el)).find('.text-field-to-copy').val(), newLink);
     });
 
     it('Confirm doc URL is updated in markup after a change', function () {
       var el = TestUtils.renderIntoDocument(<ApiBarController />, container);
-      var docLink = 'http://mydoc.org';
-      Actions.updateAPIBar({ visible: true, endpoint: 'http://whatever.com', docURL: docLink });
+      var docLink = 'http://mydoc.example.com';
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: 'http://whatever.example.com',
+        docURL: docLink,
+        contentVisible: false
+      });
 
       TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('.control-toggle-api-url')[0]);
       assert.equal($(ReactDOM.findDOMNode(el)).find('.help-link').attr('href'), docLink);
 
-      var newDocLink = 'http://newawesomedoclink.xxx';
-      Actions.updateAPIBar({ docURL: newDocLink });
+      var newDocLink = 'http://newawesomedoclink.example.com';
+      Actions.updateAPIBar({
+        buttonVisible: true,
+        endpoint: 'http://whatever.example.com',
+        docURL: newDocLink,
+        contentVisible: true
+      });
       assert.equal($(ReactDOM.findDOMNode(el)).find('.help-link').attr('href'), newDocLink);
     });
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/app/addons/components/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/components/tests/storesSpec.js b/app/addons/components/tests/storesSpec.js
deleted file mode 100644
index f74a8d6..0000000
--- a/app/addons/components/tests/storesSpec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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',
-  'testUtils',
-  'addons/components/stores',
-  'addons/components/actions'
-], function (app, FauxtonAPI, utils, Stores, ComponentActions, Resources) {
-
-  var assert = utils.assert;
-  var componentStore = Stores.componentStore;
-
-  describe('Components Store', function () {
-
-    afterEach(function () {
-      componentStore.reset();
-    });
-
-    it("UPDATE_API_BAR only updates whatever data is passed", function () {
-      var url    = 'http://whoanelly.com';
-      var docURL = 'http://website.com/docs';
-      ComponentActions.updateAPIBar({
-        visible: false,
-        endpoint: url,
-        docURL: docURL
-      });
-      assert.equal(componentStore.isAPIBarVisible(), false);
-      assert.equal(componentStore.getEndpoint(), url);
-      assert.equal(componentStore.getDocURL(), docURL);
-
-      ComponentActions.updateAPIBar({
-        visible: true
-      });
-      assert.equal(componentStore.isAPIBarVisible(), true);
-      assert.equal(componentStore.getEndpoint(), url);
-      assert.equal(componentStore.getDocURL(), docURL);
-
-      var newEndpoint = 'http://movies.com';
-      ComponentActions.updateAPIBar({
-        endpoint: newEndpoint
-      });
-      assert.equal(componentStore.isAPIBarVisible(), true);
-      assert.equal(componentStore.getEndpoint(), newEndpoint);
-      assert.equal(componentStore.getDocURL(), docURL);
-
-      var newDocURL = 'http://newwebsite.org';
-      ComponentActions.updateAPIBar({
-        docURL: newDocURL
-      });
-      assert.equal(componentStore.isAPIBarVisible(), true);
-      assert.equal(componentStore.getEndpoint(), newEndpoint);
-      assert.equal(componentStore.getDocURL(), newDocURL);
-    });
-
-  });
-
-});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index 197e46a..102f586 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -63,17 +63,22 @@ function (app, FauxtonAPI, Components, NotificationComponents, Actions, NavbarRe
       NavigationActions.setNavbarActiveLink(_.result(routeObject, 'selectedHeader'));
 
       // always attempt to render the API Bar. Even if it's hidden on initial load, it may be enabled later
-      routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController);
+      routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
+        buttonVisible: true,
+        contentVisible: false
+      });
 
       if (routeObject.get('apiUrl')) {
         var apiAndDocs = routeObject.get('apiUrl');
+
         ComponentActions.updateAPIBar({
-          visible: true,
+          buttonVisible: true,
+          contentVisible: false,
           endpoint: apiAndDocs[0],
           docURL: apiAndDocs[1]
         });
       } else {
-        ComponentActions.hideAPIBar();
+        ComponentActions.hideAPIBarButton();
       }
 
       if (!routeObject.get('hideNotificationCenter')) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/40d956f0/assets/less/trays.less
----------------------------------------------------------------------
diff --git a/assets/less/trays.less b/assets/less/trays.less
index 5f17020..f519cdf 100644
--- a/assets/less/trays.less
+++ b/assets/less/trays.less
@@ -13,7 +13,7 @@
 @import "bootstrap/mixins.less";
 @import "icons.less";
 
-
+// @deprated - old trays
 .hide-tray {
   display: none;
 }