diff --git a/app/client.jsx b/app/client.jsx
new file mode 100644
index 0000000..eb0c861
--- /dev/null
+++ b/app/client.jsx
@@ -0,0 +1,35 @@
+
+import "babel/polyfill";
+import React from "react";
+import BrowserHistory from "react-router/lib/BrowserHistory";
+import Location from "react-router/lib/Location";
+import queryString from "query-string";
+import createStore from "./redux/store";
+import ApiClient from "./helpers/ApiClient";
+import universalRouter from "./helpers/universalRouter";
+
+const history = new BrowserHistory();
+const client = new ApiClient();
+
+const dest = document.getElementById('content');
+const store = createStore(client, window.__data);
+const search = document.location.search;
+const query = search && queryString.parse(search); // TODO: research the specifics of this
+const location = new Location(document.location.pathname, query);
+
+universalRouter(location, history, store)
+ .then(({ component }) => {
+ React.render(component, dest);
+ if (__DEVTOOLS__) {
+console.log("devtois");
+ const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
+ React.render(
+ { component }
+
+
+
+
, dest);
+
+ }
+ })
+
diff --git a/app/containers/Application.jsx b/app/containers/Application.jsx
index 8bfc1bb..892f82c 100644
--- a/app/containers/Application.jsx
+++ b/app/containers/Application.jsx
@@ -1,16 +1,13 @@
import React from "react";
+import { Link } from "react-router";
import styles from "./Application.css";
-//TODO: this code sux
-import { renderDevTools } from "../../config/mainRenderer";
-
export default class Application extends React.Component {
render() {
return
-
This is an applidjfcation container
+ This is an application container
{ this.props.children }
- { renderDevTools() }
}
}
diff --git a/app/helpers/ApiClient.js b/app/helpers/ApiClient.js
new file mode 100644
index 0000000..08dc86b
--- /dev/null
+++ b/app/helpers/ApiClient.js
@@ -0,0 +1,37 @@
+import superagent from 'superagent';
+
+class ApiClient_ {
+ constructor(req) {
+ ['get', 'post', 'put', 'patch', 'del'].
+ forEach((method) => {
+ this[method] = (path, options) => {
+ return new Promise((resolve, reject) => {
+ const request = superagent[method](this.formatUrl(path));
+ if (options && options.params) {
+ request.query(options.params);
+ }
+ if (options && options.data) {
+ request.send(options.data);
+ }
+ request.end((err, res) => {
+ if (err) {
+ reject((res && res.body) || err);
+ } else {
+ resolve(res.body);
+ }
+ });
+ });
+ };
+ });
+ }
+
+ /* This was originally a standalone function outside of this class, but babel kept breaking, and this fixes it */
+ formatUrl(path) {
+ const adjustedPath = path[0] !== '/' ? '/' + path : path;
+ //TODO this will need to be adjusted
+ return '/api' + adjustedPath;
+ }
+}
+const ApiClient = ApiClient_;
+
+export default ApiClient;
diff --git a/app/helpers/universalRouter.js b/app/helpers/universalRouter.js
new file mode 100644
index 0000000..389f4bc
--- /dev/null
+++ b/app/helpers/universalRouter.js
@@ -0,0 +1,61 @@
+import React from 'react';
+import Router from 'react-router';
+import createRoutes from '../routes';
+import { Provider } from 'react-redux';
+
+const getFetchData = (component = {}) => {
+ //TODO: research this WrappedComponent attribue
+ return component.WrappedComponent ?
+ getFetchData(component.WrappedComponent) :
+ component.fetchData;
+};
+
+export function createTransitionHook(store) {
+ return (nextState, transition, callback) => {
+ const { params, location: { query } } = nextState;
+ const promises = nextState.branch
+ .map(route => route.component) // pull out individual route components
+ .filter((component) => getFetchData(component)) // only look at ones with a static fetchData()
+ .map(getFetchData) // pull out fetch data methods
+ .map(fetchData => fetchData(store, params, query || {})); // call fetch data methods and save promises
+ Promise.all(promises)
+ .then(() => {
+ callback(); // can't just pass callback to then() because callback assumes first param is error
+ }, (error) => {
+ callback(error);
+ });
+ };
+}
+
+export default function universalRouter(location, history, store) {
+ const routes = createRoutes(store);
+ return new Promise((resolve, reject) => {
+ Router.run(routes, location, [createTransitionHook(store)], (error, initialState, transition) => {
+ if (error) {
+ return reject(error);
+ }
+
+ if (transition && transition.redirectInfo) {
+ return resolve({
+ transition,
+ isRedirect: true
+ });
+ }
+
+ if (history) { // only on client side
+ initialState.history = history;
+ }
+
+ const component = (
+
+ {() => }
+
+ );
+
+ return resolve({
+ component,
+ isRedirect: false
+ });
+ });
+ });
+}
diff --git a/app/redux/middleware/clientMiddleware.js b/app/redux/middleware/clientMiddleware.js
new file mode 100644
index 0000000..eea6c5f
--- /dev/null
+++ b/app/redux/middleware/clientMiddleware.js
@@ -0,0 +1,25 @@
+export default function clientMiddleware(client) {
+ return ({dispatch, getState}) => {
+ return next => action => {
+ if (typeof action === 'function') {
+ return action(dispatch, getState);
+ }
+
+ const { promise, types, ...rest } = action;
+ if (!promise) {
+ return next(action);
+ }
+
+ const [REQUEST, SUCCESS, FAILURE] = types;
+ next({...rest, type: REQUEST});
+ return promise(client).then(
+ (result) => next({...rest, result, type: SUCCESS}),
+ (error) => next({...rest, error, type: FAILURE})
+ ).catch((error)=> {
+ console.error('MIDDLEWARE ERROR:', error);
+ next({...rest, error, type: FAILURE});
+ });
+ };
+ };
+}
+
diff --git a/app/redux/store.js b/app/redux/store.js
index e99860d..f4a2f4a 100644
--- a/app/redux/store.js
+++ b/app/redux/store.js
@@ -1,34 +1,30 @@
import { createStore, applyMiddleware, compose } from 'redux';
-
-//TODO: implement middleware for async actions
-// import createMiddleware from './middleware/clientMiddleware';
+import createMiddleware from './middleware/clientMiddleware';
export default function createApiClientStore(client, data) {
- // const middleware = createMiddleware(client);
+ const middleware = createMiddleware(client);
let finalCreateStore;
- if ( __DEVTOOLS__) {
- const { devTools, persistState } = require('redux-devtools');
- finalCreateStore = compose(
- // applyMiddleware(middleware),
- devTools(),
- persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
- )(createStore);
- }
- else {
- // finalCreateStore = applyMiddleware(middleware)(createStore);
- finalCreateStore = creteStore
+ if (__DEVTOOLS__) {
+ const { devTools, persistState } = require('redux-devtools');
+ finalCreateStore = compose(
+ applyMiddleware(middleware),
+ devTools(),
+ persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
+ )(createStore);
+ } else {
+ finalCreateStore = applyMiddleware(middleware)(createStore);
}
+
const reducer = require('./modules/reducer');
const store = finalCreateStore(reducer, data);
store.client = client;
-
- //TODO: set up hotmodule replacement
if (__DEVELOPMENT__ && module.hot) {
- module.hot.accept('./modules/reducer', () => {
- store.replaceReducer(require('./modules/reducer'));
- });
- }
+ module.hot.accept('./modules/reducer', () => {
+ store.replaceReducer(require('./modules/reducer'));
+ });
+ }
return store;
}
+
diff --git a/app/route-handlers/Application.jsx b/app/route-handlers/Application.jsx
index 367ae19..6220559 100644
--- a/app/route-handlers/Application.jsx
+++ b/app/route-handlers/Application.jsx
@@ -1,5 +1,8 @@
import Application from "containers/Application";
+
+//TODO: revist the necessity of this route-handlers abstraction
+
//TODO: eventually we will want to connect to store using react-redux;
// export default connect(mapStateToProps)(Application)
diff --git a/app/routes.jsx b/app/routes.jsx
index 9a5c62a..822bd2b 100644
--- a/app/routes.jsx
+++ b/app/routes.jsx
@@ -4,12 +4,11 @@ import { Route } from "react-router";
import Application from "route-handlers/Application";
import Login from "route-handlers/Login";
-//TODO: research necessity of Object.assign polyfill
+export default function(store) {
+ return (
+
+
+
+ )
+}
-//TODO: switch to es6 export
-
-module.exports = (
-
-
-
-)
diff --git a/config/mainRenderer.jsx b/config/mainRenderer.jsx
deleted file mode 100644
index ac520b0..0000000
--- a/config/mainRenderer.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-
-import React from "react";
-import { Router } from "react-router";
-import routes from "../app/routes";
-
-import createStore from "../app/redux/store";
-
-
-const store = createStore();
-
-export function renderDevTools() {
- if (__DEVTOOLS__) {
- const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
- return
-
-
-
- }
-}
-//TODO: will need to wrap in redux provider element
-
-React.render((
-
- { routes }
-
-), document.getElementById("content"));
-
-
diff --git a/make-webpack-config.js b/make-webpack-config.js
index 7462268..ac739a1 100644
--- a/make-webpack-config.js
+++ b/make-webpack-config.js
@@ -8,7 +8,7 @@ var loadersByExtension = require("./config/loadersByExtension");
module.exports = function(options) {
var entry = {
- main: "./config/mainRenderer"
+ main: "./app/client"
};
var loaders = {
"jsx": options.hotComponents ? ["react-hot-loader", "babel-loader?stage=0"] : "babel-loader?stage=0",
@@ -69,7 +69,7 @@ module.exports = function(options) {
if (options.debug) {
plugins.push(new webpack.DefinePlugin({
__DEVTOOLS__ : true,
- __DEVELOPMENT__: true
+ __DEVELOPMENT__: true,
}));
}
diff --git a/package.json b/package.json
index ab08f4c..3e86c2a 100644
--- a/package.json
+++ b/package.json
@@ -12,11 +12,13 @@
"author": "Matt Marcelo",
"license": "ISC",
"dependencies": {
+ "babel": "^5.8.23",
"babel-core": "^5.8.25",
"babel-loader": "^5.3.2",
"css-loader": "^0.19.0",
"extract-text-webpack-plugin": "^0.8.2",
"file-loader": "^0.8.4",
+ "history": "^1.10.1",
"html-loader": "^0.3.0",
"json-loader": "^0.5.3",
"json5": "^0.4.0",
@@ -25,15 +27,17 @@
"less-loader": "^2.2.1",
"markdown-loader": "^0.1.7",
"node-sass": "^3.3.3",
+ "query-string": "^2.4.1",
"raw-loader": "^0.5.1",
"react": "^0.13.3",
"react-hot-loader": "^1.3.0",
- "react-router": "^1.0.0-rc1",
+ "react-redux": "^3.0.0",
"redux": "^3.0.0",
"sass-loader": "^2.0.1",
"stats-webpack-plugin": "^0.2.1",
"style-loader": "^0.12.4",
"stylus-loader": "^1.3.0",
+ "superagent": "^1.4.0",
"url-loader": "^0.5.6",
"webpack": "^1.12.2"
},