You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by be...@apache.org on 2015/10/14 16:42:43 UTC

fauxton commit: updated refs/heads/master to 087e23f

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 6542a6928 -> 087e23fd4


Update to pass updates to API Bar via actions

The previous code used props on page load to set the API Bar
values (endpoint + doc URL). The problem was that because there
was no way to reference the APIBarController, it prohibited
on-the-fly updates to change the values without a route change.

This just tweaks it to store the API Bar data in a store that
can be updated by publishing an action. This will ultimately
allow us to do away with hacks like this:
https://github.com/apache/couchdb-fauxton/blob/master/app/addons/documents/header/header.react.jsx#L174

e.g. we could expand on this ticket and publish an
APIBAR_FADE_OUT” msg and the component would handle its own
fading out, rather than relying on other components directly
manipulating its DOM.


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

Branch: refs/heads/master
Commit: 087e23fd4e8237d3abdce47e8b7caed8ce95ed34
Parents: 6542a69
Author: Ben Keen <be...@gmail.com>
Authored: Fri Oct 9 12:04:15 2015 -0700
Committer: Ben Keen <be...@gmail.com>
Committed: Tue Oct 13 10:53:32 2015 -0700

----------------------------------------------------------------------
 app/addons/components/actions.js                |  45 +++++++
 app/addons/components/actiontypes.js            |  21 +++
 .../components/react-components.react.jsx       |  58 ++++++--
 app/addons/components/stores.js                 |  87 ++++++++++++
 .../tests/apiBarControllerSpec.react.jsx        | 134 +++++++++++++++++++
 app/addons/components/tests/storesSpec.js       |  68 ++++++++++
 app/addons/fauxton/base.js                      |  15 ++-
 7 files changed, 412 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/087e23fd/app/addons/components/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/components/actions.js b/app/addons/components/actions.js
new file mode 100644
index 0000000..390877c
--- /dev/null
+++ b/app/addons/components/actions.js
@@ -0,0 +1,45 @@
+// 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/components/actiontypes'
+],
+function (FauxtonAPI, ActionTypes) {
+
+  function showAPIBar () {
+    FauxtonAPI.dispatch({ type: ActionTypes.SHOW_API_BAR });
+  }
+
+  function hideAPIBar () {
+    FauxtonAPI.dispatch({ type: ActionTypes.HIDE_API_BAR });
+  }
+
+  // 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
+      }
+    });
+  }
+
+  return {
+    showAPIBar: showAPIBar,
+    hideAPIBar: hideAPIBar,
+    updateAPIBar: updateAPIBar
+  };
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/087e23fd/app/addons/components/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/components/actiontypes.js b/app/addons/components/actiontypes.js
new file mode 100644
index 0000000..c8d8f80
--- /dev/null
+++ b/app/addons/components/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 {
+    SHOW_API_BAR: 'SHOW_API_BAR',
+    HIDE_API_BAR: 'HIDE_API_BAR',
+    UPDATE_API_BAR: 'UPDATE_API_BAR'
+  };
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/087e23fd/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 41e49d2..95c5bfc 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -14,13 +14,16 @@ define([
   'app',
   'api',
   'react',
+  'addons/components/stores',
   'addons/fauxton/components.react',
   'ace/ace',
   'plugins/beautify'
 ],
 
-function (app, FauxtonAPI, React, FauxtonComponents, ace, beautifyHelper) {
+function (app, FauxtonAPI, React, Stores, FauxtonComponents, ace, beautifyHelper) {
   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+  var componentStore = Stores.componentStore;
+
 
   var ToggleHeaderButton = React.createClass({
     getDefaultProps: function () {
@@ -1076,13 +1079,32 @@ function (app, FauxtonAPI, React, FauxtonComponents, ace, beautifyHelper) {
 
   var ApiBarController = React.createClass({
 
-    getDefaultProps: function () {
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getStoreState: function () {
       return {
-        endpoint: '_all_docs',
-        documentation: FauxtonAPI.constants.DOC_URLS.GENERAL
+        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.',
@@ -1091,7 +1113,22 @@ function (app, FauxtonAPI, React, FauxtonComponents, ace, beautifyHelper) {
       });
     },
 
+    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">
 
@@ -1102,24 +1139,21 @@ function (app, FauxtonAPI, React, FauxtonComponents, ace, beautifyHelper) {
             text="API URL" />
 
           <TrayContents
-            className="api-bar-tray"
-          >
+            className="api-bar-tray">
             <div className="input-prepend input-append">
               <span className="add-on">
                 API URL
-                <a className="help-link" data-bypass="true" href={this.props.documentation} target="_blank">
-                  <i className="icon icon-question-sign"></i>
-                </a>
+                {this.getDocIcon()}
               </span>
 
               <FauxtonComponents.ClipboardWithTextField
                 onClipBoardClick={this.showCopiedMessage}
                 text="Copy"
-                textToCopy={this.props.endpoint}
+                textToCopy={this.state.endpoint}
                 uniqueKey="clipboard-apiurl" />
 
               <div className="add-on">
-                <a data-bypass="true" href={this.props.endpoint} target="_blank" className="btn">
+                <a data-bypass="true" href={this.state.endpoint} target="_blank" className="btn">
                   <i className="fonticon-eye icon"></i>
                   View JSON
                 </a>
@@ -1129,7 +1163,7 @@ function (app, FauxtonAPI, React, FauxtonComponents, ace, beautifyHelper) {
           </TrayContents>
         </Tray>
       );
-    },
+    }
   });
 
   return {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/087e23fd/app/addons/components/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/components/stores.js b/app/addons/components/stores.js
new file mode 100644
index 0000000..d5ec7d5
--- /dev/null
+++ b/app/addons/components/stores.js
@@ -0,0 +1,87 @@
+// 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',
+  'app',
+  'addons/components/actiontypes'
+],
+
+function (FauxtonAPI, app, ActionTypes) {
+  var Stores = {};
+
+
+  Stores.ComponentStore = FauxtonAPI.Store.extend({
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._apiBarVisible = true;
+      this._endpoint = '';
+      this._docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
+    },
+
+    isAPIBarVisible: function () {
+      return this._apiBarVisible;
+    },
+
+    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;
+      }
+    },
+
+    getEndpoint: function () {
+      return this._endpoint;
+    },
+
+    getDocURL: function () {
+      return this._docURL;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.SHOW_API_BAR:
+          this._apiBarVisible = true;
+          this.triggerChange();
+        break;
+
+        case ActionTypes.HIDE_API_BAR:
+          this._apiBarVisible = false;
+          this.triggerChange();
+        break;
+
+        case ActionTypes.UPDATE_API_BAR:
+          this.updateAPIBar(action.options);
+          this.triggerChange();
+        break;
+
+        default:
+        return;
+          // do nothing
+      }
+    }
+  });
+
+  Stores.componentStore = new Stores.ComponentStore();
+  Stores.componentStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.componentStore.dispatch);
+
+  return Stores;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/087e23fd/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
new file mode 100644
index 0000000..3f516d8
--- /dev/null
+++ b/app/addons/components/tests/apiBarControllerSpec.react.jsx
@@ -0,0 +1,134 @@
+// 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/components/actions',
+  'addons/components/stores',
+  'addons/components/react-components.react',
+  'testUtils',
+  'react'
+], function (FauxtonAPI, Actions, Stores, ReactComponents, utils, React) {
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+  var componentStore = Stores.componentStore;
+  var ApiBarController = ReactComponents.ApiBarController;
+
+
+  describe('ApiBarController', function () {
+    var container;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+      componentStore.reset();
+    });
+
+    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'
+      });
+      assert.equal(el.getDOMNode(), null);
+    });
+
+    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'
+      });
+      assert.notEqual(el.getDOMNode(), null);
+    });
+
+    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,
+        endpoint: '',
+        docURL: 'http://link.com'
+      });
+      assert.equal(el.getDOMNode(), null);
+    });
+
+    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.showAPIBar();
+      assert.notEqual(el.getDOMNode(), null);
+
+      Actions.hideAPIBar();
+      assert.equal(el.getDOMNode(), null);
+    });
+
+    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' });
+
+      TestUtils.Simulate.click($(el.getDOMNode()).find('.control-toggle-api-url')[0]);
+      assert.equal($(el.getDOMNode()).find('.help-link').length, 1);
+    });
+
+    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 });
+
+      TestUtils.Simulate.click($(el.getDOMNode()).find('.control-toggle-api-url')[0]);
+      assert.equal($(el.getDOMNode()).find('.help-link').length, 0);
+    });
+
+    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 });
+
+      TestUtils.Simulate.click($(el.getDOMNode()).find('.control-toggle-api-url')[0]);
+      assert.equal($(el.getDOMNode()).find('.text-field-to-copy').val(), link);
+    });
+
+    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 });
+
+      TestUtils.Simulate.click($(el.getDOMNode()).find('.control-toggle-api-url')[0]);
+      assert.equal($(el.getDOMNode()).find('.text-field-to-copy').val(), link);
+
+      var newLink = 'http://chickensarenoisy.com';
+      Actions.updateAPIBar({ endpoint: newLink });
+      assert.equal($(el.getDOMNode()).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 });
+
+      TestUtils.Simulate.click($(el.getDOMNode()).find('.control-toggle-api-url')[0]);
+      assert.equal($(el.getDOMNode()).find('.help-link').attr('href'), docLink);
+
+      var newDocLink = 'http://newawesomedoclink.xxx';
+      Actions.updateAPIBar({ docURL: newDocLink });
+      assert.equal($(el.getDOMNode()).find('.help-link').attr('href'), newDocLink);
+    });
+
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/087e23fd/app/addons/components/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/components/tests/storesSpec.js b/app/addons/components/tests/storesSpec.js
new file mode 100644
index 0000000..f74a8d6
--- /dev/null
+++ b/app/addons/components/tests/storesSpec.js
@@ -0,0 +1,68 @@
+// 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/087e23fd/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index 85cd44e..9897aae 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -19,11 +19,12 @@ define([
   "addons/fauxton/navigation/components.react",
   "addons/fauxton/navigation/actions",
   'addons/fauxton/dependencies/ZeroClipboard',
-  'addons/components/react-components.react'
+  'addons/components/react-components.react',
+  'addons/components/actions'
 ],
 
 function (app, FauxtonAPI, Components, NotificationComponents, Actions, NavbarReactComponents, NavigationActions,
-          ZeroClipboard, ReactComponents) {
+          ZeroClipboard, ReactComponents, ComponentActions) {
 
   var Fauxton = FauxtonAPI.addon();
   FauxtonAPI.addNotification = function (options) {
@@ -61,12 +62,18 @@ function (app, FauxtonAPI, Components, NotificationComponents, Actions, NavbarRe
     FauxtonAPI.RouteObject.on('beforeFullRender', function (routeObject) {
       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);
+
       if (routeObject.get('apiUrl')) {
         var apiAndDocs = routeObject.get('apiUrl');
-        routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
+        ComponentActions.updateAPIBar({
+          visible: true,
           endpoint: apiAndDocs[0],
-          documentation: apiAndDocs[1]
+          docURL: apiAndDocs[1]
         });
+      } else {
+        ComponentActions.hideAPIBar();
       }
 
       if (!routeObject.get('hideNotificationCenter')) {