This is an automated email from the ASF dual-hosted git repository.

amaranhao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/master by this push:
     new add43fc  Update fauxton/navigation to use redux (#1142)
add43fc is described below

commit add43fc566196a854e7e3021e0fd084f83b37317
Author: Antonio Maranhao <30349380+antonio-maran...@users.noreply.github.com>
AuthorDate: Mon Oct 15 21:46:04 2018 -0400

    Update fauxton/navigation to use redux (#1142)
    
    * Use redux
    
    * Update tests
---
 app/addons/fauxton/appwrapper.js                   |  44 ++--
 app/addons/fauxton/base.js                         |   4 +-
 .../navigation/__tests__/login-logout-test.js      |  47 ++--
 .../fauxton/navigation/__tests__/navbar-test.js    |  54 ++---
 .../navigation/__tests__/navigation-reducers.js    | 185 +++++++++++++++
 .../navigation/__tests__/navigation-store-test.js  | 221 ------------------
 app/addons/fauxton/navigation/actions.js           |  96 ++++----
 app/addons/fauxton/navigation/actiontypes.js       |   1 -
 app/addons/fauxton/navigation/components/NavBar.js |  23 +-
 app/addons/fauxton/navigation/container/NavBar.js  |  73 +++---
 app/addons/fauxton/navigation/reducers.js          | 209 +++++++++++++++++
 app/addons/fauxton/navigation/stores.js            | 251 ---------------------
 app/app.js                                         |  12 +-
 app/core/base.js                                   |   6 +-
 14 files changed, 553 insertions(+), 673 deletions(-)

diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js
index fca8a81..ea81a67 100644
--- a/app/addons/fauxton/appwrapper.js
+++ b/app/addons/fauxton/appwrapper.js
@@ -11,16 +11,14 @@
 // the License.
 
 import React from 'react';
+import { connect } from 'react-redux';
 import GlobalNotificationsContainer from 
'./notifications/components/GlobalNotificationsContainer';
 import NotificationPanelContainer from 
'./notifications/components/NotificationPanelContainer';
 import PermanentNotificationContainer from 
'./notifications/components/PermanentNotificationContainer';
 import NavBar from './navigation/container/NavBar';
-import NavbarActions from './navigation/actions';
-import Stores from './navigation/stores';
+import * as NavbarActions from './navigation/actions';
 import classNames from 'classnames';
 
-const navBarStore = Stores.navBarStore;
-
 class ContentWrapper extends React.Component {
   constructor(props) {
     super(props);
@@ -29,14 +27,14 @@ class ContentWrapper extends React.Component {
     };
 
     if (props.router.currentRouteOptions && 
props.router.currentRouteOptions.selectedHeader) {
-      
NavbarActions.setNavbarActiveLink(this.state.routerOptions.selectedHeader);
+      this.props.setNavbarActiveLink(this.state.routerOptions.selectedHeader);
     }
   }
 
   componentDidMount () {
     this.props.router.on('new-component', (routerOptions) => {
       this.setState({routerOptions});
-      
NavbarActions.setNavbarActiveLink(this.state.routerOptions.selectedHeader);
+      this.props.setNavbarActiveLink(this.state.routerOptions.selectedHeader);
     });
 
     this.props.router.on('trigger-update', () => {
@@ -55,31 +53,15 @@ class ContentWrapper extends React.Component {
   }
 }
 
-export default class App extends React.Component {
+class App extends React.Component {
   constructor (props) {
     super(props);
-    this.state = this.getStoreState();
-  }
-
-  getStoreState () {
-    return {
-      isPrimaryNavMinimized: navBarStore.isMinimized()
-    };
-  }
-
-  componentDidMount () {
-    navBarStore.on('change', this.onChange, this);
-  }
-
-  onChange () {
-    this.setState(this.getStoreState());
   }
 
   render () {
     const mainClass = classNames(
-      {'closeMenu': this.state.isPrimaryNavMinimized}
+      {'closeMenu': this.props.isPrimaryNavMinimized}
     );
-
     return (
       <div>
         <PermanentNotificationContainer />
@@ -91,7 +73,7 @@ export default class App extends React.Component {
           <div id="app-container">
             <div className="wrapper">
               <div className="pusher">
-                <ContentWrapper router={this.props.router} />
+                <ContentWrapper router={this.props.router} 
setNavbarActiveLink={this.props.setNavbarActiveLink}/>
               </div>
               <div id="primary-navbar">
                 <NavBar/>
@@ -103,3 +85,15 @@ export default class App extends React.Component {
     );
   }
 }
+
+export default connect(
+  ({ navigation }) => {
+    return {
+      isPrimaryNavMinimized: navigation.isMinimized};
+  },
+  (dispatch) => {
+    return {
+      setNavbarActiveLink: (link) => { 
dispatch(NavbarActions.setNavbarActiveLink(link)); }
+    };
+  }
+)(App);
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index 8a3f417..a1bb98e 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -12,7 +12,8 @@
 
 import app from '../../app';
 import FauxtonAPI from '../../core/api';
-import NavigationActions from './navigation/actions';
+import * as NavigationActions from './navigation/actions';
+import navigationReducers from './navigation/reducers';
 import notificationsReducer from './notifications/reducers';
 import './assets/less/fauxton.less';
 
@@ -32,6 +33,7 @@ Fauxton.VersionInfo = Backbone.Model.extend({
 });
 
 FauxtonAPI.addReducers({
+  navigation: navigationReducers,
   notifications: notificationsReducer
 });
 
diff --git a/app/addons/fauxton/navigation/__tests__/login-logout-test.js 
b/app/addons/fauxton/navigation/__tests__/login-logout-test.js
index 097e495..e84c2af 100644
--- a/app/addons/fauxton/navigation/__tests__/login-logout-test.js
+++ b/app/addons/fauxton/navigation/__tests__/login-logout-test.js
@@ -13,25 +13,23 @@
 
 import React from 'react';
 import { mount } from 'enzyme';
-
 import NavBar from '../components/NavBar';
 
 describe('Navigation Bar', () => {
-
+  const defaultProps = {
+    activeLink: '',
+    isMinimized: false,
+    version: '42',
+    navLinks: [],
+    bottomNavLinks: [],
+    footerNavLinks: [],
+    isNavBarVisible: true,
+    isLoginSectionVisible: true,
+    isLoginVisibleInsteadOfLogout: true,
+    toggleNavbarMenu: () => {}
+  };
   it('renders with login button when logged out', () => {
-    const props = {
-      activeLink: '',
-      isMinimized: false,
-      version: '42',
-      navLinks: [],
-      bottomNavLinks: [],
-      footerNavLinks: [],
-      isNavBarVisible: true,
-      isLoginSectionVisible: true,
-      isLoginVisibleInsteadOfLogout: true
-    };
-
-    const navBar = mount(<NavBar {...props} />);
+    const navBar = mount(<NavBar {...defaultProps} />);
 
     const button = navBar.find('[href="#/login"]');
     expect(button.text()).toContain('Login');
@@ -39,19 +37,11 @@ describe('Navigation Bar', () => {
 
   it('renders with logout button when logged in', () => {
     const props = {
-      activeLink: '',
-      isMinimized: false,
-      version: '42',
-      navLinks: [],
-      bottomNavLinks: [],
-      footerNavLinks: [],
       username: 'Rocko',
-      isNavBarVisible: true,
-      isLoginSectionVisible: true,
       isLoginVisibleInsteadOfLogout: false
     };
 
-    const navBar = mount(<NavBar {...props} />);
+    const navBar = mount(<NavBar {...defaultProps} {...props} />);
 
     const button = navBar.find('[href="#/logout"]');
     expect(button.text()).toContain('Log Out');
@@ -59,19 +49,12 @@ describe('Navigation Bar', () => {
 
   it('Admin Party has no Logout button and no Login button', () => {
     const props = {
-      activeLink: '',
-      isMinimized: false,
-      version: '42',
-      navLinks: [],
-      bottomNavLinks: [],
-      footerNavLinks: [],
       username: 'Rocko',
-      isNavBarVisible: true,
       isLoginSectionVisible: false,
       isLoginVisibleInsteadOfLogout: false
     };
 
-    const navBar = mount(<NavBar {...props} />);
+    const navBar = mount(<NavBar {...defaultProps} {...props} />);
 
     expect(navBar.text()).not.toMatch(/Login/);
     expect(navBar.text()).not.toMatch(/Log Out/);
diff --git a/app/addons/fauxton/navigation/__tests__/navbar-test.js 
b/app/addons/fauxton/navigation/__tests__/navbar-test.js
index 769b945..ba52483 100644
--- a/app/addons/fauxton/navigation/__tests__/navbar-test.js
+++ b/app/addons/fauxton/navigation/__tests__/navbar-test.js
@@ -9,58 +9,60 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations 
under
 // the License.
-import NavBarContainer from "../container/NavBar";
-import FauxtonAPI from "../../../../core/api";
-import ActionTypes from "../actiontypes";
-import React from "react";
-import ReactDOM from "react-dom";
+
 import {mount} from 'enzyme';
+import React from 'react';
+import NavBar from '../components/NavBar';
 
 describe('Navigation Bar', () => {
-  FauxtonAPI.session = {
-    user: () => {}
+  const defaultProps = {
+    isMinimized: true,
+    version: '',
+    username: '',
+    navLinks: [],
+    bottomNavLinks: [],
+    footerNavLinks: [],
+    isNavBarVisible: true,
+    isLoginSectionVisible: false,
+    isLoginVisibleInsteadOfLogout: true,
+    toggleNavbarMenu: () => {}
   };
 
   it('is displayed by default', () => {
-    const navbar = mount(<NavBarContainer />);
+    const navbar = mount(<NavBar {...defaultProps}/>);
     expect(navbar.find('.faux-navbar').length).toBe(1);
   });
 
   it('is dynamically displayed by isNavBarVisible', () => {
-    const navbar = mount(<NavBarContainer />);
+    const navbar = mount(<NavBar
+      {...defaultProps}
+      isNavBarVisible={false}/>);
 
-    FauxtonAPI.dispatch({
-      type: ActionTypes.NAVBAR_HIDE
-    });
-    navbar.update();
     expect(navbar.find('.faux-navbar').length).toBe(0);
 
-    FauxtonAPI.dispatch({
-      type: ActionTypes.NAVBAR_SHOW
-    });
+    navbar.setProps({ isNavBarVisible: true });
     navbar.update();
     expect(navbar.find('.faux-navbar').length).toBe(1);
   });
 
   it('can display items with icon badge', () => {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.ADD_NAVBAR_LINK,
-      link: {
+    const navLinks = [
+      {
         href: "#/_with_badge",
         title: "WithBadge",
         icon: "fonticon-database",
         badge: true
-      }
-    });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.ADD_NAVBAR_LINK,
-      link: {
+      },
+      {
         href: "#/_without_badge",
         title: "WithoutBadge",
         icon: "fonticon-database"
       }
-    });
-    const navbar = mount(<NavBarContainer />);
+    ];
+
+    const navbar = mount(<NavBar
+      {...defaultProps}
+      navLinks={navLinks}/>);
     expect(navbar.find('div[data-nav-name="WithoutBadge"] 
i.faux-navbar__icon-badge').length, 0);
     expect(navbar.find('div[data-nav-name="WithBadge"] 
i.faux-navbar__icon-badge').length, 1);
   });
diff --git a/app/addons/fauxton/navigation/__tests__/navigation-reducers.js 
b/app/addons/fauxton/navigation/__tests__/navigation-reducers.js
new file mode 100644
index 0000000..f2f9f12
--- /dev/null
+++ b/app/addons/fauxton/navigation/__tests__/navigation-reducers.js
@@ -0,0 +1,185 @@
+// 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.
+
+import ActionTypes from '../actiontypes';
+import reducer from '../reducers';
+
+describe('NavBar Reducer', () => {
+
+  describe('add links', () => {
+
+    it('to nav links', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'mylink'
+        }
+      };
+      const newState = reducer(undefined, action);
+      expect(newState.navLinks[0].id).toMatch(action.link.id);
+    });
+
+    it('to top nav links', () => {
+      const action1 = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: { id: 'mylink1' }
+      };
+      const action2 = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: { id: 'mylink2', top: true }
+      };
+      let newState = reducer(undefined, action1);
+      newState = reducer(newState, action2);
+
+      expect(newState.navLinks[0].id).toMatch(action2.link.id);
+    });
+
+    it('to bottom nav', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'bottomNav',
+          bottomNav: true
+        }
+      };
+      const newState = reducer(undefined, action);
+      expect(newState.bottomNavLinks[0].id).toMatch(action.link.id);
+    });
+
+    it('to top of bottom nav', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'bottomNav',
+          bottomNav: true,
+          top: true
+        }
+      };
+      const newState = reducer(undefined, action);
+      expect(newState.bottomNavLinks[0].id).toMatch(action.link.id);
+    });
+
+    it('to footer nav', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'footerNav',
+          footerNav: true
+        }
+      };
+      const newState = reducer(undefined, action);
+      expect(newState.footerNavLinks[0].id).toMatch(action.link.id);
+    });
+  });
+
+  describe('remove link', () => {
+    it('from nav links', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'remove_link'
+        }
+      };
+      let newState = reducer(undefined, action);
+      action.type = ActionTypes.REMOVE_NAVBAR_LINK;
+      newState = reducer(newState, action);
+
+      expect(newState.navLinks.length).toBe(0);
+    });
+
+    it('remove link from list only when it exists', () => {
+      const addAction = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'remove_link1',
+          footerNav: true
+        }
+      };
+      let newState = reducer(undefined, addAction);
+      addAction.link = { id: 'remove_link2', footerNav: true };
+      newState = reducer(newState, addAction);
+      addAction.link = { id: 'remove_link3', footerNav: true };
+      newState = reducer(newState, addAction);
+      expect(newState.footerNavLinks.length).toBe(3);
+
+      const removeAction = {
+        type: ActionTypes.REMOVE_NAVBAR_LINK,
+        link: addAction.link
+      };
+      newState = reducer(newState, removeAction);
+      newState = reducer(newState, removeAction);
+      newState = reducer(newState, removeAction);
+      expect(newState.footerNavLinks.length).toBe(2);
+    });
+
+    it('from bottom nav links', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'remove_link',
+          bottomNav: true
+        }
+      };
+      let newState = reducer(undefined, action);
+      action.type = ActionTypes.REMOVE_NAVBAR_LINK;
+      newState = reducer(newState, action);
+
+      expect(newState.bottomNavLinks.length).toBe(0);
+    });
+
+    it('from footer nav links', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'remove_link',
+          footerNav: true
+        }
+      };
+      let newState = reducer(undefined, action);
+      action.type = ActionTypes.REMOVE_NAVBAR_LINK;
+      newState = reducer(newState, action);
+
+      expect(newState.footerNavLinks.length).toBe(0);
+    });
+  });
+
+  describe('update link', () => {
+    it('for nav links', () => {
+      const action = {
+        type: ActionTypes.ADD_NAVBAR_LINK,
+        link: {
+          id: 'update-link',
+          title: 'first'
+        }
+      };
+      let newState = reducer(undefined, action);
+      action.type = ActionTypes.UPDATE_NAVBAR_LINK;
+      action.link.title = 'second';
+      newState = reducer(newState, action);
+
+      expect(newState.navLinks[0].title).toMatch('second');
+    });
+
+  });
+
+  describe('set version', () => {
+    it('stores version number', () => {
+      const action = {
+        type: ActionTypes.NAVBAR_SET_VERSION_INFO,
+        version: 1234
+      };
+      const newState = reducer(undefined, action);
+      expect(newState.version).toBe(1234);
+    });
+
+  });
+});
diff --git a/app/addons/fauxton/navigation/__tests__/navigation-store-test.js 
b/app/addons/fauxton/navigation/__tests__/navigation-store-test.js
deleted file mode 100644
index c664dce..0000000
--- a/app/addons/fauxton/navigation/__tests__/navigation-store-test.js
+++ /dev/null
@@ -1,221 +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.
-import FauxtonAPI from "../../../../core/api";
-import Stores from "../stores";
-const navBarStore = Stores.navBarStore;
-
-describe('NavBarStore', () => {
-  beforeEach(() => {
-    FauxtonAPI.dispatch({
-      type: 'CLEAR_NAVBAR_LINK',
-    });
-
-  });
-
-  describe('add links', () => {
-
-    it('to nav links', () => {
-      var link = {
-        id: 'mylink'
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getNavLinks()[0].id).toMatch(link.id);
-    });
-
-    it('to top nav links', () => {
-      var link1 = {
-        id: 'mylink1'
-      };
-
-      var link2 = {
-        id: 'mylink2',
-        top: true
-      };
-
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link1
-      });
-
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link2
-      });
-
-      expect(navBarStore.getNavLinks()[0].id).toMatch(link2.id);
-    });
-
-    it('to bottom nav', () => {
-      var link = {
-        id: 'bottomNav',
-        bottomNav: true
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id);
-    });
-
-    it('to top of bottom nav', () => {
-      var link = {
-        id: 'bottomNav',
-        bottomNav: true,
-        top: true
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id);
-    });
-
-    it('to footer nav', () => {
-      var link = {
-        id: 'footerNav',
-        footerNav: true
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getFooterNavLinks()[0].id).toMatch(link.id);
-    });
-  });
-
-  describe('remove link', () => {
-    it('from nav links', () => {
-      var link = {
-        id: 'remove_link',
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      FauxtonAPI.dispatch({
-        type: 'REMOVE_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getNavLinks().length).toBe(0);
-    });
-
-    it('remove link from list', () => {
-      const addLink = (id) => {
-        FauxtonAPI.dispatch({
-          type: 'ADD_NAVBAR_LINK',
-          link: {
-            id: id,
-            footerNav: true
-          }
-        });
-      };
-      const removeLink = () => {
-        FauxtonAPI.dispatch({
-          type: 'REMOVE_NAVBAR_LINK',
-          link: {
-            id: 'remove_link3',
-            footerNav: true
-          }
-        });
-      };
-      addLink('remove_link1');
-      addLink('remove_link2');
-      addLink('remove_link3');
-
-      removeLink();
-      removeLink();
-      removeLink();
-
-      expect(navBarStore.getFooterNavLinks().length).toBe(2);
-    });
-
-    it('from bottom nav links', () => {
-      var link = {
-        id: 'remove_link',
-        bottomNav: true
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      FauxtonAPI.dispatch({
-        type: 'REMOVE_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getBottomNavLinks().length).toBe(0);
-    });
-
-    it('from footer nav links', () => {
-      var link = {
-        id: 'remove_link',
-        footerNav: true
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      FauxtonAPI.dispatch({
-        type: 'REMOVE_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getFooterNavLinks().length).toBe(0);
-    });
-  });
-
-  describe('update link', () => {
-    it('for nav links', () => {
-      var link = {
-        id: 'update-link',
-        title: 'first'
-      };
-      FauxtonAPI.dispatch({
-        type: 'ADD_NAVBAR_LINK',
-        link: link
-      });
-
-      link.title = 'second';
-
-      FauxtonAPI.dispatch({
-        type: 'UPDATE_NAVBAR_LINK',
-        link: link
-      });
-
-      expect(navBarStore.getNavLinks()[0].title).toMatch('second');
-    });
-
-  });
-
-  describe('set version', () => {
-    it('stores version number', () => {
-      FauxtonAPI.dispatch({
-        type: 'NAVBAR_SET_VERSION_INFO',
-        version: 1234
-      });
-
-      expect(navBarStore.getVersion()).toBe(1234);
-    });
-
-  });
-});
diff --git a/app/addons/fauxton/navigation/actions.js 
b/app/addons/fauxton/navigation/actions.js
index 3548157..ac26091 100644
--- a/app/addons/fauxton/navigation/actions.js
+++ b/app/addons/fauxton/navigation/actions.js
@@ -10,53 +10,51 @@
 // License for the specific language governing permissions and limitations 
under
 // the License.
 
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "./actiontypes";
-
-export default {
-  toggleNavbarMenu () {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.TOGGLE_NAVBAR_MENU
-    });
-  },
-
-  addHeaderLink (link) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.ADD_NAVBAR_LINK,
-      link: link
-    });
-  },
-
-  removeHeaderLink (link) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.REMOVE_NAVBAR_LINK,
-      link: link
-    });
-  },
-
-  setNavbarVersionInfo (versionInfo) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.NAVBAR_SET_VERSION_INFO,
-      version: versionInfo
-    });
-  },
-
-  setNavbarActiveLink (header) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.NAVBAR_ACTIVE_LINK,
-      name: header
-    });
-  },
-
-  showNavBar () {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.NAVBAR_SHOW
-    });
-  },
-
-  hideNavBar () {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.NAVBAR_HIDE
-    });
-  }
+import FauxtonAPI from '../../../core/api';
+import ActionTypes from './actiontypes';
+
+export const toggleNavbarMenu = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.TOGGLE_NAVBAR_MENU
+  });
+};
+
+export const addHeaderLink = (link) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.ADD_NAVBAR_LINK,
+    link: link
+  });
+};
+
+export const removeHeaderLink = (link) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.REMOVE_NAVBAR_LINK,
+    link: link
+  });
+};
+
+export const setNavbarVersionInfo = (versionInfo) => {
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.NAVBAR_SET_VERSION_INFO,
+    version: versionInfo
+  });
+};
+
+export const setNavbarActiveLink = (header) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.NAVBAR_ACTIVE_LINK,
+    name: header
+  });
+};
+
+export const showNavBar = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.NAVBAR_SHOW
+  });
+};
+
+export const hideNavBar = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.NAVBAR_HIDE
+  });
 };
diff --git a/app/addons/fauxton/navigation/actiontypes.js 
b/app/addons/fauxton/navigation/actiontypes.js
index 2a5d72f..e007062 100644
--- a/app/addons/fauxton/navigation/actiontypes.js
+++ b/app/addons/fauxton/navigation/actiontypes.js
@@ -14,7 +14,6 @@ export default {
   ADD_NAVBAR_LINK: 'ADD_NAVBAR_LINK',
   TOGGLE_NAVBAR_MENU: 'TOGGLE_NAVBAR_MENU',
   UPDATE_NAVBAR_LINK: 'UPDATE_NAVBAR_LINK',
-  CLEAR_NAVBAR_LINK: 'CLEAR_NAVBAR_LINK',
   REMOVE_NAVBAR_LINK: 'REMOVE_NAVBAR_LINK',
   HIDE_NAVBAR_LINK_BADGE: 'HIDE_NAVBAR_LINK_BADGE',
   SHOW_NAVBAR_LINK_BADGE: 'SHOW_NAVBAR_LINK_BADGE',
diff --git a/app/addons/fauxton/navigation/components/NavBar.js 
b/app/addons/fauxton/navigation/components/NavBar.js
index 77cb3ab..89d0c48 100644
--- a/app/addons/fauxton/navigation/components/NavBar.js
+++ b/app/addons/fauxton/navigation/components/NavBar.js
@@ -10,10 +10,9 @@
 // License for the specific language governing permissions and limitations 
under
 // the License.
 
+import classNames from 'classnames';
 import PropTypes from 'prop-types';
-
 import React, { Component } from 'react';
-
 import Footer from './Footer';
 import Burger from './Burger';
 import NavLink from './NavLink';
@@ -21,12 +20,12 @@ import Brand from './Brand';
 import LogoutButton from './LogoutButton';
 import LoginButton from './LoginButton';
 
-import Actions from "../actions";
-
-import classNames from 'classnames';
-
 class NavBar extends Component {
 
+  constructor(props) {
+    super(props);
+  }
+
   createLinks (links) {
     const { activeLink, isMinimized } = this.props;
 
@@ -39,10 +38,6 @@ class NavBar extends Component {
     });
   }
 
-  toggleMenu () {
-    Actions.toggleNavbarMenu();
-  }
-
   render () {
     const {
       isMinimized,
@@ -51,7 +46,8 @@ class NavBar extends Component {
       isLoginVisibleInsteadOfLogout,
       activeLink,
       username,
-      isNavBarVisible
+      isNavBarVisible,
+      toggleNavbarMenu
     } = this.props;
 
     if (!isNavBarVisible) {
@@ -76,7 +72,7 @@ class NavBar extends Component {
       <div className={navClasses}>
         <nav>
           <div className="faux-navbar__linkcontainer">
-            <Burger isMinimized={isMinimized} toggleMenu={this.toggleMenu}/>
+            <Burger isMinimized={isMinimized} toggleMenu={toggleNavbarMenu}/>
             <div className="faux-navbar__links">
               {navLinks}
               {bottomNavLinks}
@@ -110,7 +106,8 @@ NavBar.propTypes = {
   footerNavLinks: PropTypes.array,
   isNavBarVisible: PropTypes.bool,
   isLoginSectionVisible: PropTypes.bool.isRequired,
-  isLoginVisibleInsteadOfLogout: PropTypes.bool.isRequired
+  isLoginVisibleInsteadOfLogout: PropTypes.bool.isRequired,
+  toggleNavbarMenu: PropTypes.func.isRequired
 };
 
 export default NavBar;
diff --git a/app/addons/fauxton/navigation/container/NavBar.js 
b/app/addons/fauxton/navigation/container/NavBar.js
index d5efb13..2703b79 100644
--- a/app/addons/fauxton/navigation/container/NavBar.js
+++ b/app/addons/fauxton/navigation/container/NavBar.js
@@ -10,55 +10,38 @@
 // License for the specific language governing permissions and limitations 
under
 // the License.
 
-import FauxtonAPI from "../../../../core/api";
-import React from "react";
-
-import ReactDOM from "react-dom";
-import Stores from "../stores";
-
+import { connect } from 'react-redux';
+import FauxtonAPI from '../../../../core/api';
 import NavBar from '../components/NavBar';
-
-const navBarStore = Stores.navBarStore;
-
-class NavBarContainer extends React.Component {
-  getStoreState = () => {
-    return {
-      navLinks: navBarStore.getNavLinks(),
-      bottomNavLinks: navBarStore.getBottomNavLinks(),
-      footerNavLinks: navBarStore.getFooterNavLinks(),
-      activeLink: navBarStore.getActiveLink(),
-      version: navBarStore.getVersion(),
-      isMinimized: navBarStore.isMinimized(),
-      isNavBarVisible: navBarStore.isNavBarVisible(),
-
-      isLoginSectionVisible: navBarStore.getIsLoginSectionVisible(),
-      isLoginVisibleInsteadOfLogout: 
navBarStore.getIsLoginVisibleInsteadOfLogout()
-    };
+import * as Actions from '../actions';
+
+const mapStateToProps = ({ navigation }) => {
+  const user = FauxtonAPI.session.user();
+  return {
+    navLinks: navigation.navLinks,
+    bottomNavLinks: navigation.bottomNavLinks,
+    footerNavLinks: navigation.footerNavLinks,
+    activeLink: navigation.activeLink,
+    version: navigation.version,
+    isMinimized: navigation.isMinimized,
+    isNavBarVisible: navigation.navBarVisible,
+    isLoginSectionVisible: navigation.loginSectionVisible,
+    isLoginVisibleInsteadOfLogout: navigation.loginVisibleInsteadOfLogout,
+    username: (user && user.name) ? user.name : ''
   };
+};
 
-  onChange = () => {
-    this.setState(this.getStoreState());
+const mapDispatchToProps = (dispatch) => {
+  return {
+    toggleNavbarMenu: () => {
+      dispatch(Actions.toggleNavbarMenu());
+    }
   };
+};
 
-  state = this.getStoreState();
-
-  componentDidMount() {
-    navBarStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    navBarStore.off('change', this.onChange);
-  }
-
-  render() {
-    const user = FauxtonAPI.session.user();
-
-    const username =  (user && user.name) ? user.name : '';
-    return (
-      <NavBar {...this.state} username={username} />
-    );
-  }
-}
-
+const NavBarContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(NavBar);
 
 export default NavBarContainer;
diff --git a/app/addons/fauxton/navigation/reducers.js 
b/app/addons/fauxton/navigation/reducers.js
new file mode 100644
index 0000000..6af5d2c
--- /dev/null
+++ b/app/addons/fauxton/navigation/reducers.js
@@ -0,0 +1,209 @@
+// 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.
+
+import ActionTypes from './actiontypes';
+
+const initialState = {
+  isMinimized: true,
+  activeLink: null,
+  version: null,
+  navLinks: [],
+  footerNavLinks: [],
+  bottomNavLinks: [],
+  navBarVisible: true,
+  loginSectionVisible: false,
+  loginVisibleInsteadOfLogout: true
+};
+
+function addLink(state, link) {
+  const newState = { ...state };
+
+  if (link.top && !link.bottomNav) {
+    newState.navLinks = [].concat(newState.navLinks);
+    newState.navLinks.unshift(link);
+    return newState;
+  }
+  if (link.top && link.bottomNav) {
+    newState.bottomNavLinks = [].concat(newState.bottomNavLinks);
+    newState.bottomNavLinks.unshift(link);
+    return newState;
+  }
+  if (link.bottomNav) {
+    newState.bottomNavLinks = [].concat(newState.bottomNavLinks);
+    newState.bottomNavLinks.push(link);
+    return newState;
+  }
+  if (link.footerNav) {
+    newState.footerNavLinks = [].concat(newState.footerNavLinks);
+    newState.footerNavLinks.push(link);
+    return newState;
+  }
+
+  newState.navLinks = [].concat(newState.navLinks);
+  newState.navLinks.push(link);
+  return newState;
+}
+
+function removeLink (state, removeLink) {
+  const {links, sectionName} = getLinkSection(state, removeLink);
+
+  // create new array without the link to remove
+  const newLinks = links.filter(link => link.id !== removeLink.id);
+
+  if (newLinks.length === links.length) {
+    return state;
+  }
+
+  const newState = { ...state };
+  newState[sectionName] = newLinks;
+  return newState;
+}
+
+function updateLink (state, link) {
+  const {links, sectionName} = getLinkSection(state, link);
+
+  // create new array and updates the link when found
+  let found = false;
+  const newLinks = links.map(el => {
+    if (el.id === link.id) {
+      found = true;
+      return {
+        ...el,
+        title: link.title,
+        href: link.href
+      };
+    }
+    return el;
+  });
+
+  if (!found) {
+    return state;
+  }
+
+  const newState = { ...state };
+  newState[sectionName] = newLinks;
+  return newState;
+}
+
+function setLinkBadgeVisible (state, link, visible) {
+  const {links, sectionName} = getLinkSection(state, link);
+
+  let found = false;
+  const newLinks = links.map(el => {
+    if (el.title === link.title) {
+      found = true;
+      return {
+        ...el,
+        badge: visible
+      };
+    }
+    return el;
+  });
+
+  if (!found) {
+    return state;
+  }
+
+  const newState = { ...state };
+  newState[sectionName] = newLinks;
+  return newState;
+}
+
+function getLinkSection (state, link) {
+  let links = state.navLinks;
+  let sectionName = 'navLinks';
+
+  if (link.bottomNav) {
+    links = state.bottomNavLinks;
+    sectionName = 'bottomNavLinks';
+  }
+
+  if (link.footerNav) {
+    links = state.footerNavLinks;
+    sectionName = 'footerNavLinks';
+  }
+
+  return { links, sectionName };
+}
+
+export default function navigation(state = initialState, action) {
+  switch (action.type) {
+
+    case ActionTypes.ADD_NAVBAR_LINK:
+      return addLink(state, action.link);
+
+    case ActionTypes.TOGGLE_NAVBAR_MENU:
+      return {
+        ...state,
+        isMinimized: !state.isMinimized
+      };
+
+    case ActionTypes.UPDATE_NAVBAR_LINK:
+      return updateLink(state, action.link);
+
+    case ActionTypes.REMOVE_NAVBAR_LINK:
+      return removeLink(state, action.link);
+
+    case ActionTypes.SHOW_NAVBAR_LINK_BADGE:
+      return setLinkBadgeVisible(state, action.link, true);
+
+    case ActionTypes.HIDE_NAVBAR_LINK_BADGE:
+      return setLinkBadgeVisible(state, action.link, false);
+
+    case ActionTypes.NAVBAR_SET_VERSION_INFO:
+      return {
+        ...state,
+        version: action.version
+      };
+
+    case ActionTypes.NAVBAR_ACTIVE_LINK:
+      return {
+        ...state,
+        activeLink: action.name
+      };
+
+    case ActionTypes.NAVBAR_HIDE:
+      return {
+        ...state,
+        navBarVisible: false
+      };
+
+    case ActionTypes.NAVBAR_SHOW:
+      return {
+        ...state,
+        navBarVisible: true
+      };
+
+    case ActionTypes.NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION:
+      return {
+        ...state,
+        loginSectionVisible: action.visible
+      };
+
+    case ActionTypes.NAVBAR_SHOW_LOGIN_BUTTON:
+      return {
+        ...state,
+        loginSectionVisible: true,
+        loginVisibleInsteadOfLogout: true
+      };
+
+    case ActionTypes.NAVBAR_SHOW_LOGOUT_BUTTON:
+      return {
+        ...state,
+        loginSectionVisible: true,
+        loginVisibleInsteadOfLogout: false
+      };
+
+    default:
+      return state;
+  }
+}
diff --git a/app/addons/fauxton/navigation/stores.js 
b/app/addons/fauxton/navigation/stores.js
deleted file mode 100644
index d08f243..0000000
--- a/app/addons/fauxton/navigation/stores.js
+++ /dev/null
@@ -1,251 +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.
-
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "./actiontypes";
-import {findIndex} from 'lodash';
-
-const Stores = {};
-
-Stores.NavBarStore = FauxtonAPI.Store.extend({
-  initialize () {
-    this.reset();
-  },
-
-  reset () {
-    this._isMinimized = true;
-    this._activeLink = null;
-    this._version = null;
-    this._navLinks = [];
-    this._footerNavLinks = [];
-    this._bottomNavLinks = [];
-    this._navBarVisible = true;
-
-    this._loginSectionVisible = false;
-    this._loginVisibleInsteadOfLogout = true;
-  },
-
-  getIsLoginSectionVisible () {
-    return this._loginSectionVisible;
-  },
-
-  getIsLoginVisibleInsteadOfLogout () {
-    return this._loginVisibleInsteadOfLogout;
-  },
-
-  isNavBarVisible () {
-    return this._navBarVisible;
-  },
-
-  showNavBar () {
-    this._navBarVisible = true;
-  },
-
-  hideNavBar () {
-    this._navBarVisible = false;
-  },
-
-  addLink (link) {
-    if (link.top && !link.bottomNav) {
-      this._navLinks.unshift(link);
-      return;
-    }
-    if (link.top && link.bottomNav) {
-      this._bottomNavLinks.unshift(link);
-      return;
-    }
-    if (link.bottomNav) {
-      this._bottomNavLinks.push(link);
-      return;
-    }
-    if (link.footerNav) {
-      this._footerNavLinks.push(link);
-      return;
-    }
-
-    this._navLinks.push(link);
-  },
-
-  removeLink (removeLink) {
-    const links = this.getLinkSection(removeLink);
-
-    const indexOf = findIndex(links, link => {
-      if (link.id === removeLink.id) {
-        return true;
-      }
-
-      return false;
-    });
-
-    if (indexOf === -1) { return; }
-
-    links.splice(indexOf, 1);
-  },
-
-  getNavLinks () {
-    return this._navLinks;
-  },
-
-  getBottomNavLinks () {
-    return this._bottomNavLinks;
-  },
-
-  getFooterNavLinks () {
-    return this._footerNavLinks;
-  },
-
-  toggleMenu () {
-    this._isMinimized = !this._isMinimized;
-  },
-
-  getLinkSection (link) {
-    let links = this._navLinks;
-
-    if (link.bottomNav) {
-      links = this._bottomNavLinks;
-    }
-
-    if (link.footerNav) {
-      links = this._footerNavLinks;
-    }
-
-    return links;
-  },
-
-  updateLink (link) {
-    let oldLink;
-    const links = this.getLinkSection(link);
-
-    oldLink = _.find(links, function (oldLink) {
-      return oldLink.id === link.id;
-    });
-
-    if (!oldLink) { return; }
-
-    oldLink.title = link.title;
-    oldLink.href = link.href;
-  },
-
-  showLinkBadge (link) {
-    const links = this.getLinkSection(link);
-    const selectedLink = links.find(function (oldLink) {
-      return oldLink.title === link.title;
-    });
-    if (selectedLink) {
-      selectedLink.badge = true;
-    }
-  },
-
-  hideLinkBadge (link) {
-    const links = this.getLinkSection(link);
-    const selectedLink = links.find(function (oldLink) {
-      return oldLink.title === link.title;
-    });
-
-    if (selectedLink) {
-      selectedLink.badge = false;
-    }
-  },
-
-  getVersion () {
-    return this._version;
-  },
-
-  setVersion (version) {
-    this._version = version;
-  },
-
-  getActiveLink () {
-    return this._activeLink;
-  },
-
-  setActiveLink (activeLink) {
-    this._activeLink = activeLink;
-  },
-
-  isMinimized () {
-    return this._isMinimized;
-  },
-
-  dispatch (action) {
-    switch (action.type) {
-      case ActionTypes.ADD_NAVBAR_LINK:
-        this.addLink(action.link);
-        break;
-
-      case ActionTypes.TOGGLE_NAVBAR_MENU:
-        this.toggleMenu();
-        break;
-
-      case ActionTypes.UPDATE_NAVBAR_LINK:
-        this.updateLink(action.link);
-        break;
-
-      case ActionTypes.CLEAR_NAVBAR_LINK:
-        this.reset();
-        break;
-
-      case ActionTypes.REMOVE_NAVBAR_LINK:
-        this.removeLink(action.link);
-        break;
-
-      case ActionTypes.SHOW_NAVBAR_LINK_BADGE:
-        this.showLinkBadge(action.link);
-        break;
-
-      case ActionTypes.HIDE_NAVBAR_LINK_BADGE:
-        this.hideLinkBadge(action.link);
-        break;
-
-      case ActionTypes.NAVBAR_SET_VERSION_INFO:
-        this.setVersion(action.version);
-        break;
-
-      case ActionTypes.NAVBAR_ACTIVE_LINK:
-        this.setActiveLink(action.name);
-        break;
-
-      case ActionTypes.NAVBAR_HIDE:
-        this.hideNavBar();
-        break;
-
-      case ActionTypes.NAVBAR_SHOW:
-        this.showNavBar();
-        break;
-
-      case ActionTypes.NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION:
-        this._loginSectionVisible = action.visible;
-        break;
-
-      case ActionTypes.NAVBAR_SHOW_LOGIN_BUTTON:
-        this._loginSectionVisible = true;
-        this._loginVisibleInsteadOfLogout = true;
-        break;
-
-      case ActionTypes.NAVBAR_SHOW_LOGOUT_BUTTON:
-        this._loginSectionVisible = true;
-        this._loginVisibleInsteadOfLogout = false;
-        break;
-
-      default:
-        return;
-      // do nothing
-    }
-
-    this.triggerChange();
-  }
-});
-
-Stores.navBarStore = new Stores.NavBarStore();
-Stores.navBarStore.dispatchToken = 
FauxtonAPI.dispatcher.register(Stores.navBarStore.dispatch.bind(Stores.navBarStore));
-
-export default Stores;
diff --git a/app/app.js b/app/app.js
index 6c39160..ed6d994 100644
--- a/app/app.js
+++ b/app/app.js
@@ -38,41 +38,41 @@ FauxtonAPI.config({
   // I haven't wrapped these dispatch methods in a action
   // because I don't want to require fauxton/actions in this method.
   addHeaderLink: function (link) {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'ADD_NAVBAR_LINK',
       link: link
     });
   },
 
   updateHeaderLink: function (link) {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'UPDATE_NAVBAR_LINK',
       link: link
     });
   },
 
   removeHeaderLink: function (link) {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'REMOVE_NAVBAR_LINK',
       link: link
     });
   },
 
   hideLogin: function () {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION',
       visible: false
     });
   },
 
   showLogout: function () {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'NAVBAR_SHOW_LOGOUT_BUTTON'
     });
   },
 
   showLogin: function () {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'NAVBAR_SHOW_LOGIN_BUTTON'
     });
   }
diff --git a/app/core/base.js b/app/core/base.js
index 0762f7a..2278d84 100644
--- a/app/core/base.js
+++ b/app/core/base.js
@@ -24,19 +24,19 @@ var FauxtonAPI = {
   // I haven't wrapped these dispatch methods in a action
   // because I don't want to require fauxton/actions in this method.
   addHeaderLink: function (link) {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'ADD_NAVBAR_LINK',
       link: link
     });
   },
   showHeaderLinkBadge: function (link) {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'SHOW_NAVBAR_LINK_BADGE',
       link: link
     });
   },
   hideHeaderLinkBadge: function (link) {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: 'HIDE_NAVBAR_LINK_BADGE',
       link: link
     });

Reply via email to