Skip to content

Commit

Permalink
implement beginning of simpleRouter with support for hashes
Browse files Browse the repository at this point in the history
  • Loading branch information
irisli committed Feb 4, 2016
1 parent cc7c9dc commit 3140d1d
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -13,3 +13,6 @@ npm install
npm install
./node_modules/.bin/gulp build
```

## Internal documentation
The [docs.md](./docs.md) file contains code documentation on the laboratory. The docs.md is only relevant for developing the laboratory.
23 changes: 23 additions & 0 deletions docs.md
@@ -0,0 +1,23 @@
This file contains information about the laboratory itself and design choices inside.

## Simple Router
The laboratory uses a simple routing system with no outside dependencies. The simple router is stateless. Components do not need to know about the router since everything is handled.

### Dataflow: Two possible events
#### Outbound changes: Changes from updated redux store
When the redux state changes, we can then serialize it and save it in the url. This is handled by a middleware called `routerMiddleware`.

This is reactive and won't change the redux store.

#### Inbound changes: Changes from url hash updates
The a component uses the `routerListener` to provide access to dispatching actions. It will perform a diff to see if an the store needs to be updated. If so, it will dispatch an event.

If save data is not specified, the redux store should not be cleared.

### User navigation via clicks on plain hash links
User navigation should always occur through hash links. This is so that users can open links in new tabs as well as right clicking to copy the link.

Although these events are user initiated, these are considered "inbound" changes since it is changing the hash which is outside of redux.

### Routing actions and reducer
The only code creating actions for routing.js should be the routerListener.
7 changes: 7 additions & 0 deletions src/actions/routing.js
@@ -0,0 +1,7 @@
export const UPDATE_LOCATION = "UPDATE_LOCATION";
export function updateLocation(location) {
return {
type: UPDATE_LOCATION,
location
}
}
4 changes: 3 additions & 1 deletion src/app.js
Expand Up @@ -7,7 +7,8 @@ import thunk from 'redux-thunk';
import {Provider} from 'react-redux';

import rootReducer from './reducers/root';
import logging from "./middleware/logging";
import logging from './middleware/logging';
import {routerMiddleware} from './utilities/simpleRouter';
import LaboratoryChrome from './components/LaboratoryChrome';


Expand All @@ -17,6 +18,7 @@ document.write('<div id="app"></div>');

let createStoreWithMiddleware = applyMiddleware(
thunk,
routerMiddleware,
logging
)(createStore);

Expand Down
2 changes: 2 additions & 0 deletions src/components/LaboratoryChrome.js
Expand Up @@ -5,6 +5,7 @@ import NetworkPicker from './NetworkPicker';
import EndpointExplorer from './EndpointExplorer';
import TransactionBuilder from './TransactionBuilder';
import TransactionSigner from './TransactionSigner';
import {RouterListener} from '../utilities/simpleRouter';

let LaboratoryChrome = React.createClass({
getInitialState: function() {
Expand Down Expand Up @@ -68,6 +69,7 @@ let LaboratoryChrome = React.createClass({
</div>

{activeTab}
<RouterListener />
</div>;
}
});
Expand Down
2 changes: 2 additions & 0 deletions src/reducers/root.js
Expand Up @@ -3,12 +3,14 @@ import endpointExplorer from './endpointExplorer';
import transactionBuilder from './transactionBuilder';
import transactionSigner from './transactionSigner';
import network from './network';
import routing from './routing';

const rootReducer = combineReducers({
endpointExplorer,
transactionBuilder,
transactionSigner,
network,
routing,
});

export default rootReducer;
20 changes: 20 additions & 0 deletions src/reducers/routing.js
@@ -0,0 +1,20 @@
import {combineReducers} from 'redux';
import {UPDATE_LOCATION} from '../actions/routing';
import url from 'url';

const routing = combineReducers({
location,
});

export default routing;

function location(state, action) {
if (typeof state === 'undefined') {
// During first load, we want to reduce FOUC by bootstrapping here
return url.parse(window.location.hash.substr(1)).pathname || '';
}
if (action.type === UPDATE_LOCATION) {
return action.location;
}
return state;
}
40 changes: 40 additions & 0 deletions src/utilities/simpleRouter.js
@@ -0,0 +1,40 @@
import {connect} from 'react-redux';
import React from 'react';
import url from 'url';
import {updateLocation} from '../actions/routing';

export const routerMiddleware = store => next => action => {
return next(action);
}

class RouterHashListener extends React.Component {
componentWillMount() {
window.addEventListener('hashchange', this.hashChangeHandler.bind(this), false);
}
componentWillUnmount() {
// Just doing our duty of cleanup though it's really not necessary
window.removeEventListener('hashchange', this.hashChangeHandler.bind(this), false);
}
hashChangeHandler(e) {
let oldUrlHash = url.parse(e.oldURL).hash || '';
let newUrlHash = url.parse(e.newURL).hash || '';

// The "real" url is inside the hash;
let oldUrl = url.parse(oldUrlHash.substr(1));
let newUrl = url.parse(newUrlHash.substr(1));

if (oldUrl.pathname !== newUrl.pathname) {
this.props.dispatch(updateLocation(newUrl.pathname));
}
}
render() {
return null;
}
}

export let RouterListener = connect(chooseState, dispatch => ({ dispatch }))((RouterHashListener));
function chooseState(state) {
return {
routing: state.routing
};
}

0 comments on commit 3140d1d

Please sign in to comment.