|
@@ -0,0 +1,155 @@
|
|
|
+/** @module Container */
|
|
|
+
|
|
|
+// Import dependencies
|
|
|
+import React from 'react'
|
|
|
+import { createStore, applyMiddleware, combineReducers, bindActionCreators, compose } from 'redux'
|
|
|
+import { Provider, connect } from 'react-redux'
|
|
|
+import createSagaMiddleware from 'redux-saga'
|
|
|
+import { all } from 'redux-saga/effects'
|
|
|
+import { browserHistory, Router, Route, IndexRoute } from 'react-router'
|
|
|
+import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
|
|
|
+
|
|
|
+class Container {
|
|
|
+ constructor () {
|
|
|
+ this.states = {}
|
|
|
+ this.actionCreators = {}
|
|
|
+ this.reducers = {}
|
|
|
+ this.routes = []
|
|
|
+ this.sagas = []
|
|
|
+ this.middlewares = []
|
|
|
+ }
|
|
|
+
|
|
|
+ addModule (module) {
|
|
|
+ if (typeof module !== 'object') {
|
|
|
+ throw Error('Modules must be an object.')
|
|
|
+ }
|
|
|
+ if (!module.name || typeof module.name !== 'string') {
|
|
|
+ throw Error('Module object must have a name property')
|
|
|
+ }
|
|
|
+ const name = module.name
|
|
|
+ if (module.state) {
|
|
|
+ this.states[name] = module.state
|
|
|
+ }
|
|
|
+ if (module.reducer) {
|
|
|
+ this.reducers[name] = module.reducer
|
|
|
+ }
|
|
|
+ if (module.actionCreators) {
|
|
|
+ this.actionCreators[name] = module.actionCreators
|
|
|
+ }
|
|
|
+ if (module.route) {
|
|
|
+ this.routes.push(module.route)
|
|
|
+ }
|
|
|
+ if (module.saga) {
|
|
|
+ this.sagas.push(module.saga)
|
|
|
+ }
|
|
|
+ if (module.middleware) {
|
|
|
+ this.middlewares.push(module.middleware)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ createApp (appClass) {
|
|
|
+ const withRedux = (this.reducers.length > 0 || this.actionCreators.length > 0 || this.states.length > 0)
|
|
|
+ const withRouter = (this.routes.length > 0)
|
|
|
+ const withSagas = (this.sagas.length > 0)
|
|
|
+ const withMiddleware = (this.middlewares.length > 0)
|
|
|
+
|
|
|
+ /** Check for available enhancers and combine all middleware */
|
|
|
+ const enhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
|
|
+ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(
|
|
|
+ applyMiddleware(...this.middleware))
|
|
|
+ : compose(applyMiddleware(...this.middleware))
|
|
|
+
|
|
|
+ if (withRedux) {
|
|
|
+ /** The default state is combined from all sub-module states */
|
|
|
+ const defaultState = { ...this.states }
|
|
|
+ /** The root reducer is combined from all sub-module reducers */
|
|
|
+ const rootReducer = combineReducers({ ...this.reducers, routing: routerReducer })
|
|
|
+ /** The action creators of all sub-modules are combined */
|
|
|
+ const actionCreators = { ...this.actionCreators }
|
|
|
+ const store = createStore(rootReducer, defaultState, enhancer)
|
|
|
+ const history = syncHistoryWithStore(browserHistory, store)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** The root saga, calling all other sagas */
|
|
|
+ function * rootSaga () {
|
|
|
+ yield all(this.sagas.map(saga => saga()))
|
|
|
+ }
|
|
|
+ if (withSagas) {
|
|
|
+ const sagaMiddleware = createSagaMiddleware()
|
|
|
+ this.middlewares.push(sagaMiddleware)
|
|
|
+ sagaMiddleware.run(rootSaga)
|
|
|
+ }
|
|
|
+
|
|
|
+ function mapStateToProps (state) {
|
|
|
+ const propState = {}
|
|
|
+ Object.keys(state).forEach(key => {
|
|
|
+ propState[key] = state[key]
|
|
|
+ })
|
|
|
+ return propState
|
|
|
+ }
|
|
|
+
|
|
|
+ function mapDispatchToProps (dispatch) {
|
|
|
+ const boundActionCreators = {}
|
|
|
+ Object.keys(actionCreators).forEach(key => {
|
|
|
+ boundActionCreators[`${key}Actions`] = bindActionCreators(actionCreators[key], dispatch)
|
|
|
+ })
|
|
|
+ return boundActionCreators
|
|
|
+ }
|
|
|
+ const App = connect(mapStateToProps, mapDispatchToProps)(appClass)
|
|
|
+
|
|
|
+ let provider
|
|
|
+ if (withRouter) {
|
|
|
+ provider = (
|
|
|
+ <Provider store={store}>
|
|
|
+ <Router history={history}>
|
|
|
+ <Route component={App}>
|
|
|
+ <Route path='/'>
|
|
|
+ <IndexRoute component={demo_module.components.DemoModule} />
|
|
|
+ {this.routes.map(route => route)}
|
|
|
+ </Route>
|
|
|
+ </Route>
|
|
|
+ </Router>
|
|
|
+ </Provider>
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ provider = (
|
|
|
+ <Provider store={store}>
|
|
|
+ <App />
|
|
|
+ </Provider>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ return provider
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default Container
|
|
|
+
|
|
|
+/** react-router is not used in this project.
|
|
|
+*/
|
|
|
+
|
|
|
+/**
|
|
|
+ * Redux Section
|
|
|
+ */
|
|
|
+/** The enhancer allows to use Redux development tools in Chrome */
|
|
|
+// see: https://github.com/zalmoxisus/redux-devtools-extension/issues/220
|
|
|
+
|
|
|
+/** Build the Redux store from the rootReducer, the defualtState and the enhancers. */
|
|
|
+/** react-route is not used in this project.
|
|
|
+
|
|
|
+*/
|
|
|
+
|
|
|
+/** Collect the action creators from all modules in actionCreators */
|
|
|
+
|
|
|
+/** Creates a function */
|
|
|
+
|
|
|
+/**
|
|
|
+ * React-Router Section
|
|
|
+ **/
|
|
|
+
|
|
|
+/** Combine the routes from all modules.
|
|
|
+
|
|
|
+*/
|
|
|
+
|
|
|
+/**
|
|
|
+ * Render the app
|
|
|
+ **/
|