silverstripe-framework/admin/client/src/boot/index.js

132 lines
4.3 KiB
JavaScript
Raw Normal View History

import BootRoutes from './BootRoutes';
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
Use redux-form instead of custom actions/reducers (fixes #5524) - Removed custom form reducers in favour of redux-form (updateField(), addForm(), etc) - Storing 'state' in schema reducer to avoid a separate forms reducer tree (no longer needed for other use cases). This means 'state' is only the "initial state", any form edits will be handled by redux-form internally in the 'reduxForm' reducer namespace - Removed componentWillUnmount() from <Form> since there's no more reducer state to clean up (removed formReducer.js), and redux-form handles that internally - Removed isFetching state from <FormBuilder> since there's now a props.submitting coming from redux-form - Improved passing of "clicked button" (submittingAction), using component state rather than reducer and passing it into action handlers (no need for components to inspect it any other way) - Hacky duplication of this.submittingAction and this.state.submittingAction to avoid re-render of <FormBuilder> *during* a submission (see https://github.com/erikras/redux-form/issues/1944) - Inlined handleSubmit() XHR (rather than using a redux action). There's other ways for form consumers to listen to form evens (e.g. onSubmitSuccess passed through <FormBuilder> into reduxForm()). - Adapting checkbox/radio fields to redux-forms Need to send onChange event with values rather than the original event, see http://redux-form.com/6.1.1/docs/api/Field.md/#-input-onchange-eventorvalue-function- - Using reduxForm() within render() causes DOM to get thrown away, and has weird side effects like https://github.com/erikras/redux-form/issues/1944. See https://github.com/erikras/redux-form/issues/603#issuecomment-176397728 - Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation - Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent) - Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
2016-10-12 03:47:14 +02:00
import { reducer as ReduxFormReducer } from 'redux-form';
import { routerReducer } from 'react-router-redux';
import Config from 'lib/Config';
Correct naming for JS and CSS files in client/ Removed some dist/js/*.js files since they're no longer built as individual files. This was a side effect of them living in the toplevel folder of admin/client/src/, which used to have all the legacy/*.js files in there (they do need to be built). Following AirBnB convention: https://github.com/airbnb/javascript#naming--filename-matches-export While it technically allows index.js files, we found them to be bad for dev and debugging in practice: Depending on the used IDE, editor tabs all look the same. Other views like Chrome Dev Tools with sourcemaps rely on path context, and are harder to auto-complete. There's no direct rules for CSS files, but same principles apply here. Also renamed the sections/ folder to containers/, which more clearly communicates the distinction between components/ (shouldn't contain state-dependant, smart components). Renamed state/ files to follow AirBnB naming conventions https://github.com/airbnb/javascript#naming--filename-matches-export https://github.com/airbnb/javascript#naming--camelCase-default-export https://github.com/airbnb/javascript#naming--PascalCase-singleton Leaving the folder name in state/<state-key> lowercase since that's also the key to reducers in the actual state object. References: http://engineering.kapost.com/2016/01/organizing-large-react-applications/ https://github.com/erikras/react-redux-universal-hot-example/tree/master/src https://github.com/RickWong/react-isomorphic-starterkit/tree/master/src https://github.com/react-toolbox/react-toolbox/issues/98 https://github.com/react-bootstrap/react-bootstrap/tree/master/src
2016-04-21 11:59:44 +02:00
import reducerRegister from 'lib/ReducerRegister';
import * as configActions from 'state/config/ConfigActions';
import ConfigReducer from 'state/config/ConfigReducer';
import SchemaReducer from 'state/schema/SchemaReducer';
import RecordsReducer from 'state/records/RecordsReducer';
import CampaignReducer from 'state/campaign/CampaignReducer';
import BreadcrumbsReducer from 'state/breadcrumbs/BreadcrumbsReducer';
import bootInjector from 'boot/BootInjector';
2016-09-21 07:14:43 +02:00
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { printRequest } from 'apollo-client/transport/networkInterface';
import qs from 'qs';
2016-03-22 04:27:44 +01:00
// Sections
// eslint-disable-next-line no-unused-vars
import CampaignAdmin from 'containers/CampaignAdmin/controller';
import es6promise from 'es6-promise';
es6promise.polyfill();
function appBoot() {
2016-09-21 07:14:43 +02:00
const baseUrl = Config.get('absoluteBaseUrl');
const networkInterface = createNetworkInterface({
uri: `${baseUrl}graphql/`,
opts: {
credentials: 'same-origin',
},
});
2016-09-21 07:14:43 +02:00
const apolloClient = new ApolloClient({
shouldBatch: true,
addTypename: true,
dataIdFromObject: (o) => {
if (o.id >= 0 && o.__typename) {
2016-09-21 07:14:43 +02:00
return `${o.__typename}:${o.id}`;
}
return null;
},
networkInterface,
2016-09-21 07:14:43 +02:00
});
networkInterface.use([{
applyMiddleware(req, next) {
const entries = printRequest(req.request);
// eslint-disable-next-line no-param-reassign
req.options.headers = Object.assign(
{},
req.options.headers,
{
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
);
// eslint-disable-next-line no-param-reassign
req.options.body = qs.stringify(Object.assign(
{},
entries,
{ variables: JSON.stringify(entries.variables) }
));
next();
},
}]);
reducerRegister.add('config', ConfigReducer);
Use redux-form instead of custom actions/reducers (fixes #5524) - Removed custom form reducers in favour of redux-form (updateField(), addForm(), etc) - Storing 'state' in schema reducer to avoid a separate forms reducer tree (no longer needed for other use cases). This means 'state' is only the "initial state", any form edits will be handled by redux-form internally in the 'reduxForm' reducer namespace - Removed componentWillUnmount() from <Form> since there's no more reducer state to clean up (removed formReducer.js), and redux-form handles that internally - Removed isFetching state from <FormBuilder> since there's now a props.submitting coming from redux-form - Improved passing of "clicked button" (submittingAction), using component state rather than reducer and passing it into action handlers (no need for components to inspect it any other way) - Hacky duplication of this.submittingAction and this.state.submittingAction to avoid re-render of <FormBuilder> *during* a submission (see https://github.com/erikras/redux-form/issues/1944) - Inlined handleSubmit() XHR (rather than using a redux action). There's other ways for form consumers to listen to form evens (e.g. onSubmitSuccess passed through <FormBuilder> into reduxForm()). - Adapting checkbox/radio fields to redux-forms Need to send onChange event with values rather than the original event, see http://redux-form.com/6.1.1/docs/api/Field.md/#-input-onchange-eventorvalue-function- - Using reduxForm() within render() causes DOM to get thrown away, and has weird side effects like https://github.com/erikras/redux-form/issues/1944. See https://github.com/erikras/redux-form/issues/603#issuecomment-176397728 - Refactored <FormBuilderLoader> as a separate container component which connects to redux and redux-form. This keeps the <FormBuilder> component dependency free and easy to test. It'll also be an advantage if we switch to a GraphQL backed component, in which case the async loading routines will look very different from the current <FormBuilderLoader> implementation - Refactoring out the redux-form dependency from FormBuilder to make it more testable (through baseFormComponent and baseFieldComponent) - Passing through 'form' to allow custom identifiers (which is important because currently the schema "id" contains the record identifier, making the form identifier non-deterministic - which means you can't use it with the redux-form selector API)
2016-10-12 03:47:14 +02:00
reducerRegister.add('form', ReduxFormReducer);
reducerRegister.add('schemas', SchemaReducer);
reducerRegister.add('records', RecordsReducer);
2016-04-12 00:24:16 +02:00
reducerRegister.add('campaign', CampaignReducer);
reducerRegister.add('breadcrumbs', BreadcrumbsReducer);
reducerRegister.add('routing', routerReducer);
2016-09-21 07:14:43 +02:00
reducerRegister.add('apollo', apolloClient.reducer());
bootInjector.start();
2016-03-22 04:27:44 +01:00
const initialState = {};
const rootReducer = combineReducers(reducerRegister.getAll());
2016-09-21 07:14:43 +02:00
const middleware = [
thunkMiddleware,
apolloClient.middleware(),
];
const env = Config.get('environment');
const debugging = Config.get('debugging');
let runMiddleware = applyMiddleware(...middleware);
// use browser extension `compose` function if it's available
const composeExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
// use browser extension devTools if it's available
// this is the old way: `devToolsExtension` is being deprecated
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ || window.devToolsExtension;
if (env === 'dev' && debugging) {
if (typeof composeExtension === 'function') {
// use compose from extension first
runMiddleware = composeExtension(applyMiddleware(...middleware));
} else if (typeof devTools === 'function') {
// fallback to old way
runMiddleware = compose(applyMiddleware(...middleware), devTools());
}
}
const createStoreWithMiddleware = runMiddleware(createStore);
const store = createStoreWithMiddleware(rootReducer, initialState);
2016-03-22 04:27:44 +01:00
// Set the initial config state.
store.dispatch(configActions.setConfig(Config.getAll()));
// Expose store for legacy use
window.ss = window.ss || {};
window.ss.store = store;
2016-09-21 07:14:43 +02:00
// Expose client for legacy use
window.ss = window.ss || {};
window.ss.apolloClient = apolloClient;
// Bootstrap routing
2016-09-21 07:14:43 +02:00
const routes = new BootRoutes(store, apolloClient);
routes.start(window.location.pathname);
// @TODO - Remove once we remove entwine
// Enable top-level css selectors for react-dependant entwine sections
2016-12-16 01:41:36 +01:00
if (window.jQuery) {
window.jQuery('body').addClass('js-react-boot');
}
}
window.onload = appBoot;