diff --git a/src/js/frontend/package.json b/src/js/frontend/package.json index bfad919..30c01f6 100644 --- a/src/js/frontend/package.json +++ b/src/js/frontend/package.json @@ -20,6 +20,7 @@ "dependencies": { "@n3dst4/browser-bundle": "^1.1.0", "@n3dst4/build-stylesheets": "^1.0.1", + "axios": "^0.15.3", "babel-polyfill": "6.9.1", "browser-sync": "^2.12.3", "d3": "^4.3.0", @@ -38,7 +39,9 @@ "react-redux": "^4.4.5", "react-router": "^2.4.1", "redux": "^3.6.0", + "redux-devtools-extension": "^2.13.0", "redux-logger": "^2.6.1", + "redux-promise-middleware": "^4.2.0", "redux-storage": "^4.0.1", "redux-storage-decorator-debounce": "^1.0.1", "redux-storage-engine-localstorage": "^1.1.1", diff --git a/src/js/frontend/src/Redux/action-types.js b/src/js/frontend/src/Redux/action-types.js index e0a0eb6..c8a84ab 100644 --- a/src/js/frontend/src/Redux/action-types.js +++ b/src/js/frontend/src/Redux/action-types.js @@ -1,2 +1,8 @@ export const TOGGLE_ABOUT = "TOGGLE_ABOUT" + export const ADD_ENTITY = "ADD_ENTITY" + +export const FETCH_ENTITIES = "FETCH_ENTITIES" +export const FETCH_ENTITIES_PENDING = "FETCH_ENTITIES_PENDING" +export const FETCH_ENTITIES_FULFILLED = "FETCH_ENTITIES_FULFILLED" +export const FETCH_ENTITIES_REJECTED = "FETCH_ENTITIES_REJECTED" diff --git a/src/js/frontend/src/Redux/actions.js b/src/js/frontend/src/Redux/actions.js index 3c0b300..4d03560 100644 --- a/src/js/frontend/src/Redux/actions.js +++ b/src/js/frontend/src/Redux/actions.js @@ -1,4 +1,5 @@ import * as actionTypes from "./action-types" +import entityData from "../lib/entityData" // These are action creators // http://redux.js.org/docs/basics/Actions.html#action-creators @@ -17,3 +18,17 @@ export const addEntity = (entityType, id, data) => { data, } } + +export const fetchEntities = (entityType) => { + const request = entityData.fetchEntitiesRequest(entityType); + + // When dispatched, redux-promise-middleware will handle this and + // magically dispatch FETCH_ENTITIES_{PENDING,FULFILLED,REJECTED} + // actions according to the 3 possible stages of the conversation + // with the API endpoint. + return { + type: actionTypes.FETCH_ENTITIES, + meta: { entityType }, + payload: request + } +} diff --git a/src/js/frontend/src/Redux/store.js b/src/js/frontend/src/Redux/store.js index 23518d4..7c9e70b 100644 --- a/src/js/frontend/src/Redux/store.js +++ b/src/js/frontend/src/Redux/store.js @@ -5,11 +5,13 @@ import {rootReducer, storeStructure, initialStoreState} from "./reducer" import * as storage from "redux-storage" import createEngine from "redux-storage-engine-localstorage" import storageDebounce from 'redux-storage-decorator-debounce' +import promiseMiddleware from 'redux-promise-middleware' import immutablejs from 'redux-storage-decorator-immutablejs' import merger from 'redux-storage-merger-immutablejs' //////////////////////////////////////////////////////////////////////////////// // redux stuff +import { composeWithDevTools } from 'redux-devtools-extension' // wrap our main reducer in a storage reducer - this intercepts LOAD actions and // calls the merger function to merge in the new state @@ -31,18 +33,22 @@ const loggerMiddleware = createLogger({ //predicate: (getState, action) => action.type !== CALCULATION_NEEDS_REFRESH }) -// now create our redux store, applying all our middleware -// const store = createStore(reducer, applyMiddleware( -// thunkMiddleware, // first, so function results get transformed -// loggerMiddleware, // now log everything at this state -// storageMiddleware // finally the storage middleware -// )) - +const composeEnhancers = composeWithDevTools({ + name: 'MUTI frontend', + // actionsBlacklist: ['REDUX_STORAGE_SAVE'] +}); const store = createStore( reducer, initialStoreState, - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() + composeEnhancers( + applyMiddleware( + thunkMiddleware, // first, so function results get transformed + promiseMiddleware(), +// loggerMiddleware, // now log everything at this state +// storageMiddleware, // finally the storage middleware + ) + ) ); window.store = store; diff --git a/src/js/frontend/src/components/Home.js b/src/js/frontend/src/components/Home.js index 040807a..7a34ce6 100644 --- a/src/js/frontend/src/components/Home.js +++ b/src/js/frontend/src/components/Home.js @@ -17,6 +17,23 @@ class OrgList extends React.Component { +

Here are sample URLs you can use to visualize parts of the data set relating to a given organisation. They assume that diff --git a/src/js/frontend/src/containers/EntityListContainer.js b/src/js/frontend/src/containers/EntityListContainer.js new file mode 100644 index 0000000..1b577bd --- /dev/null +++ b/src/js/frontend/src/containers/EntityListContainer.js @@ -0,0 +1,44 @@ +import React from "react" +import Radium from "radium" +import { connect } from 'react-redux'; + +import * as actions from "../Redux/actions" +import entityData from "../lib/entityData" + +class EntityListContainer extends React.Component { + componentWillMount () { + this.props.fetchEntities(this.props.route.entityType); + } + + render () { + let entityType = this.props.route.entityType; + + if (! entityData.isValidType(entityType)) { + return

+ Invalid entity type {entityType}. + Must be one of: {entityData.validTypes.join(", ")} +
+ } + + return
+ List of {entityType} entities +
+ } +} + +// const mapStateToProps = (state) => { +// return { +// entities: state.get("entities"), +// }; +// }; + +const mapDispatchToProps = (dispatch) => { + return { + fetchEntities: (entityType) => { + dispatch(actions.fetchEntities(entityType)); + } + } +} + +export default connect(null, mapDispatchToProps)(Radium(EntityListContainer)); + diff --git a/src/js/frontend/src/lib/api.js b/src/js/frontend/src/lib/api.js index 89e3afc..b47cadc 100644 --- a/src/js/frontend/src/lib/api.js +++ b/src/js/frontend/src/lib/api.js @@ -8,6 +8,10 @@ let api = { URL: function (path) { return this.protocol + "://" + this.host + ":" + this.port + this.apiPathPrefix + path; + }, + + fetchEntitiesURL: function (entityType) { + return this.URL(entityType); } } diff --git a/src/js/frontend/src/lib/entityData.js b/src/js/frontend/src/lib/entityData.js index 66c4625..b929f50 100644 --- a/src/js/frontend/src/lib/entityData.js +++ b/src/js/frontend/src/lib/entityData.js @@ -1,7 +1,21 @@ let d3 = require('d3'); let api = require('./api'); +import axios from "axios" let entityData = { + apiURL: function(entityType, entityId) { + return api.URL(entityType + "/" + entityId); + }, + + validTypes: [ + "organisations", "people", "government-offices" + ], + + isValidType: function(entityType) { + return this.validTypes.indexOf(entityType) >= 0; + }, + + // FIXME: retire this in favour of fetchEntitiesRequest() below fetch: function(entityType, entityId, onSuccess) { let url = this.apiURL(entityType, entityId); console.debug("Fetching from " + url); @@ -12,10 +26,6 @@ let entityData = { .get(this.dataLoadedHandler(onSuccess, url, entityType, entityId)); }, - apiURL: function(entityType, entityId) { - return api.URL(entityType + "/" + entityId); - }, - dataLoadedHandler: function(onSuccess, url, entityType, entityId) { let self = this; @@ -39,6 +49,15 @@ let entityData = { onSuccess(json); }; }, + + fetchEntitiesRequest: function (entityType) { + return axios.get(api.fetchEntitiesURL(entityType), { + headers: { + "Content-Type": "application/json", + "Accept": "application/vnd.api+json" + }, + }); + } } module.exports = entityData; diff --git a/src/js/frontend/src/main.js b/src/js/frontend/src/main.js index b0dca48..32f7c91 100644 --- a/src/js/frontend/src/main.js +++ b/src/js/frontend/src/main.js @@ -9,6 +9,7 @@ import App from "./app" import PageLayout from "./containers/PageLayout" import Home from "./components/Home" import ChartContainer from "./containers/ChartContainer" +import EntityListContainer from "./containers/EntityListContainer" // now everything is set up, create a loader and use it to load the store const load = storage.createLoader(engine) @@ -25,6 +26,12 @@ loaded.then((newState) => { + + +