|
@@ -1,118 +1,147 @@
|
|
|
/**
|
|
|
* state.js
|
|
|
*
|
|
|
- * Collection of everything which has to do with state changes.
|
|
|
- * - actionTypes
|
|
|
+ * The application uses Redux to handle the state.
|
|
|
+ * Redux has a **store** which allows access to the one big state of the
|
|
|
+ * modules. It also has a dispatcher, which receives actions and calls
|
|
|
+ * reducer functions to act on them.
|
|
|
+ *
|
|
|
+ * The store handles all state changes. For that, a module
|
|
|
+ * defines **action creators**, which are helper functions generating action
|
|
|
+ * objects. An action object contains a 'type' property, which gives the action
|
|
|
+ * a name, and a payload, which will be processed.
|
|
|
+ *
|
|
|
+ * The processing unit is called a **reducer** and is exported as 'reducer' in
|
|
|
+ * this file. It is a function which receives
|
|
|
+ * the current state and an action and returns the processed state. It is good
|
|
|
+ * practice, that the reducer doesn't change the state (which is passed by
|
|
|
+ * reference), but creates a modified state and returns it. Also, the reducer
|
|
|
+ * should return immediately. If an asynchronous operation has to be executed,
|
|
|
+ * it is handled through redux-sagas, which is basically a concurrent process
|
|
|
+ * triggered by a reducer, which generates a new action once it's completed.
|
|
|
+ *
|
|
|
+ * Action creators (defined as 'actions' in this file) are bound to the
|
|
|
+ * Redux dispatcher in 'index.js'. When they are called, the dispatcher sends
|
|
|
+ * the greated action to **all** reducers. This module, and other modules which
|
|
|
+ * change the state of this module, use these action creators to modifiy the
|
|
|
+ * state.
|
|
|
+ *
|
|
|
+ * This file exports everything which is necessary to set up and operate the
|
|
|
+ * state of this module.
|
|
|
* - actions
|
|
|
* - reducers
|
|
|
+ * - state
|
|
|
+ * - sagas
|
|
|
**/
|
|
|
|
|
|
+
|
|
|
+/* Import NAME and DATA to be able to create action creators */
|
|
|
import { NAME, DATA } from './constants'
|
|
|
-import { primary, secondary2 } from './initialData'
|
|
|
-// import { call, put, takeEvery } from 'redux-saga/effects'
|
|
|
+import primary from './initialData'
|
|
|
+import { delay } from 'redux-saga'
|
|
|
+import { call, put, takeEvery } from 'redux-saga/effects'
|
|
|
+
|
|
|
|
|
|
/** actionTypes define what actions are handeled by the reducer. */
|
|
|
-export const actionTypes = {}
|
|
|
-export const actions = {}
|
|
|
+const actionTypes = {}
|
|
|
+export const actionCreators = {}
|
|
|
|
|
|
-// Generate default actionsTypes CREATE, UPDATE, DELETE for every data type
|
|
|
-DATA.forEach(data => {
|
|
|
+// Generate default actionsTypes CREATE, UPDATE, REMOVE
|
|
|
+DATA.forEach(dataItem => {
|
|
|
['create', 'update', 'delete'].forEach(action => {
|
|
|
- const actionType = `${action.toUpperCase()}_${data.toUpperCase()}`
|
|
|
- const actionName = `${action}${data[0].toUpperCase()}${data.substring(1)}`
|
|
|
+ // The Redux convention is to name action types e.g. demo_module/UPDATE
|
|
|
+ // For action creators, we define here the name e.g. removePrimary(id, data)
|
|
|
+ // where id is the element id and data is the element itself.
|
|
|
+ const actionType = `${action.toUpperCase()}_${dataItem.toUpperCase()}`
|
|
|
+ const actionName = `${action}${dataItem[0].toUpperCase()}${dataItem[0].substring(1)}`
|
|
|
+ actionCreators[actionName] = (id, data) => { return { type: actionType, id, data } }
|
|
|
actionTypes[actionType] = `${NAME}/${actionType}`
|
|
|
- actions[actionName] = (id, data) => { return { type: actionType, id, data } }
|
|
|
})
|
|
|
})
|
|
|
+/* in our case we generate e.g.
|
|
|
+createPrimary (id, data) {
|
|
|
+ // id will be ignored, data will be inserted into state.primary
|
|
|
+}
|
|
|
+updateSecondary1 (id, data) {
|
|
|
+ // id is used to find the secondary item. data is the new value of the item.
|
|
|
+}
|
|
|
+removeSecondary2 (id, data) {
|
|
|
+ // id is used to find the secondary item.
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
+// Add specific action creators here:
|
|
|
+actionCreators['loadSamples'] = () => { return { type: `${NAME}/LOAD_SAMPLES` } }
|
|
|
+console.log('State actionTypes, actionCreators', actionTypes, actionCreators)
|
|
|
|
|
|
-// Add specific actionTypes/actions
|
|
|
-actionTypes['LOAD_SAMPLES'] = `${NAME}/LOAD_SAMPLES`
|
|
|
-actions['loadSamples'] = () => { return { type: `${NAME}/LOAD_SAMPLES` } }
|
|
|
-console.log('State actionTypes, actions', actionTypes, actions)
|
|
|
|
|
|
/** state definition */
|
|
|
/** It is generally easier to not have another object here. If you can combine everything into
|
|
|
* the primary data, do it. */
|
|
|
-export const state = {
|
|
|
- primary: [],
|
|
|
- secondary2: []
|
|
|
-}
|
|
|
+export const state = []
|
|
|
console.log('State state', state)
|
|
|
|
|
|
+
|
|
|
/** reducer is called by the redux dispatcher and handles all component actions */
|
|
|
-function secondaryReducer (state = {}, action) {
|
|
|
- console.log(`Entering registermap setting reducer.`, state, action)
|
|
|
+/** The module's root reducer tries to split up the state and let specific
|
|
|
+ * specialized reducers handle the work. This is not necessary, but more
|
|
|
+ * convenient and readable. */
|
|
|
+ // In this example it is assumed, that the secondary items have a key 'primaryId',
|
|
|
+ // which references the primary data element.
|
|
|
+export function reducer (state = [], action) {
|
|
|
+ console.log(`Entering demo_module root reducer.`, state, action)
|
|
|
+ const idx = state.findIndex(elem => {return (action.id === elem.id)})
|
|
|
+ if (action.type.match(/SECONDARY/)) {
|
|
|
+ // leave immediately, if no idx was found (operations on secondary not valid )
|
|
|
+ if (idx < 0) {
|
|
|
+ return state
|
|
|
+ }
|
|
|
+ console.log(`Using secondary reducer.`)
|
|
|
+ const subState = state.secondary
|
|
|
+ const reducedState = secondaryReducer(subState, action)
|
|
|
+ const nextState = {
|
|
|
+ ...state,
|
|
|
+ secondary: reducedState
|
|
|
+ }
|
|
|
+ console.log('Leaving demo_module root reducer', subState, reducedState, nextState)
|
|
|
+ return nextState
|
|
|
+ }
|
|
|
switch (action.type) {
|
|
|
- case actionTypes.CREATE_SETTING:
|
|
|
- console.log(`Creating setting.`, state, action)
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- [action.project.projectId]: action.project
|
|
|
- }
|
|
|
- case actionTypes.UPDATE_SETTING:
|
|
|
- console.log(`Update setting.`, state, action)
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- [action.projectId]: action.project
|
|
|
- }
|
|
|
- case actionTypes.DELETE_SETTING:
|
|
|
- console.log(`Delete setting.`, state, action)
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- [action.projectId]: null
|
|
|
- }
|
|
|
+ case actionTypes.CREATE_PRIMARY:
|
|
|
+ return state
|
|
|
+ case actionTypes.UPDATE_PRIMARY:
|
|
|
+ return state
|
|
|
+ case actionTypes.REMOVE_PRIMARY:
|
|
|
+ return state
|
|
|
+ case actionTypes.LOAD_SAMPLES:
|
|
|
+ return state
|
|
|
default:
|
|
|
return state
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function primaryReducer (state = {}, action) {
|
|
|
- console.log(`Entering registermap reducer.`, state, action)
|
|
|
+function secondaryReducer (state = [], action) {
|
|
|
+ console.log(`Entering secondary reducer.`, state, action)
|
|
|
+ const idx = state.findIndex(elem => {return (action.id === elem.id)})
|
|
|
+ let nextState
|
|
|
switch (action.type) {
|
|
|
- case actionTypes.CREATE_REGISTERMAP:
|
|
|
- console.log(`Creating registermap.`, state, action)
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- [action.project.projectId]: action.project
|
|
|
- }
|
|
|
- case actionTypes.UPDATE_REGISTERMAP:
|
|
|
- console.log(`Update registermap.`, state, action)
|
|
|
- const idx = state.findIndex(elem => { return (elem.id === action.registermapId) })
|
|
|
- console.log(`Update registermap.`, idx)
|
|
|
- const next = [
|
|
|
- ...state.slice(0, idx),
|
|
|
- action.registermap,
|
|
|
- ...state.slice(idx + 1)
|
|
|
- ]
|
|
|
- console.log(`Update registermap.`, next)
|
|
|
- return next
|
|
|
- case actionTypes.DELETE_REGISTERMAP:
|
|
|
- console.log(`Delete registermap.`, state, action)
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- [action.projectId]: null
|
|
|
- }
|
|
|
+ case actionTypes.CREATE_SECONDARY:
|
|
|
+ nextState = [ ...state, action.data ]
|
|
|
+ console.log(`Creating secondary.`, state, nextState)
|
|
|
+ return nextState
|
|
|
+ case actionTypes.UPDATE_SECONDARY:
|
|
|
+ nextState = [ ...state.slice(0, idx), action.data, ...state.slice(idx + 1)]
|
|
|
+ console.log(`Updating secondary.`, idx, state, nextState)
|
|
|
+ return nextState
|
|
|
+ case actionTypes.DELETE_SECONDARY:
|
|
|
+ nextState = [ ...state.slice(0, idx), ...state.slice(idx + 1) ]
|
|
|
+ console.log(`Deleting secondary.`, idx, state, nextState)
|
|
|
+ return nextState
|
|
|
default:
|
|
|
return state
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function reducer (state = {}, action) {
|
|
|
- console.log(`Entering registermap root reducer.`, state, action)
|
|
|
- if (typeof action.settingId !== 'undefined') {
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- registermap: secondaryReducer(state.registermap, action)
|
|
|
- }
|
|
|
- }
|
|
|
- if (typeof action.registermapId !== 'undefined') {
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- registermap: primaryReducer(state.registermap, action)
|
|
|
- }
|
|
|
- }
|
|
|
- return state
|
|
|
-}
|
|
|
console.log('State reducer', reducer)
|
|
|
|
|
|
/** sagas are asynchronous workers (JS generators) to handle the state.
|