CXBOX UI
CXBOX - Rapid Enterprise Level Application Development Platform
Website | Demo | Documentation
Description
CXBOX main purpose is to speed up development of typical Enterprise Level Application based on Spring Boot. A fixed contract with a user interface called Cxbox-UI allows backend developer to create typical interfaces providing just Json meta files. Full set of typical Enterprise Level UI components included - widgets, fields, layouts (views), navigation (screens).
Using CXBOX
- download Intellij Plugin adds platform specific autocomplete, inspection, navigation and code generation features.
- download Demo and follow README.md instructions. Feel free to use demo as template project to start your own projects
CXBOX UI
Main concepts
@cxbox-ui/core is a typescript library that includes set of prebuilt pieces of Redux ecosystem, to correctly interact with CXBox framework. It contains all parts to combine Redux store inside of your React application.
- Actions
- Reducers
- Middlewares
- Epics
- RxJS methods for asynchronous interaction with CXBOX framework
- Api
- Wrapped in RxJS observables
- Utilities
- Interfaces
- CXBOX interaction typescript contract
Changelog
Getting started
The best way to start CXBox project is to clone CXBox-Demo and follow the README instructions
Manual installation
@cxbox-ui/core distributed in form of ESM* npm package:
yarn add @cxbox-ui/core
or
npm install @cxbox-ui/core
Several libraries are specified as peer dependencies and should be installed
- react
- react-dom
- @reduxjs/toolkit
- rxjs
- redux-observable
- axios
[!WARNING] CJS module system are no longer supported
Usage
Library proposes to use common way of configuring your Redux store with some nuances
How to configure default Redux store
Reducers
Proper way of creating reducers
@cxbox-ui/core exports all arguments for @reduxjs/toolkit createReducer
method, such as initialState
and createReducerBuilderManager
instances of default CXBox reducers
import {reducers} from '@cxbox-ui/core'
import {createReducer} from '@reduxjs/toolkit'
const viewReducerBuilder = reducers
.createViewReducerBuilderManager(reducers.initialViewState)
.builder
const viewReducer = createReducer(reducers.initialViewState, viewReducerBuilder)
ReducerBuildManager
implements default methods of createReducer
builder callback argument, with removeCase
and replaceCase
const viewReducerBuilder = reducers
.createViewReducerBuilderManager(reducers.initialViewState)
.removeCase('sampleCXBoxAction')
.addCase('anotherSampleAction', () => {/** do something with state */})
.replaceCase('someCXBoxActionToo', () => {/** in case of CXBox realization is not satisfying */})
.builder
const viewReducer = createReducer(reducers.initialViewState, viewReducerBuilder)
More appropriate case samples in CXBox-Demo
Combine reducers
import {combineReducers, configureStore} from '@reduxjs/toolkit'
const rootReducer = combineReducers({
screen: screenReducer,
data: dataReducer,
view: viewReducer,
session: sessionReducer,
router: routerReducer,
notification: notificationReducer
})
const store = configureStore({
reducers: rootReducer
})
Redux-observable middleware
To make this store work with CXBox backend, you should configure asynchronous interaction by applying preconfigured Redux-Observable Epics
It will take two steps
- Configure Api Axios instance
import {Api} from '@cxbox-ui/core'
import Axios from 'axios'
const __AJAX_TIMEOUT__ = 900000
const __CLIENT_ID__: number = Date.now()
const HEADERS = { Pragma: 'no-cache', 'Cache-Control': 'no-cache, no-store, must-revalidate' }
const instance = axios.create({
baseURL: __API__,
timeout: __AJAX_TIMEOUT__,
responseType: 'json',
headers: {
...HEADERS,
...{ ClientId: __CLIENT_ID__ }
}
})
const CXBoxApiInstance = new Api(instance)
You can also extend any of Api methods to use in your epics
import {Api} from '@cxbox-ui/core'
class ExtendedApi extends Api {
getSomethingUnusual(thing: string) {
return this.$api.get(thing)
}
}
const CXBoxApiInstance = new ExtendedApi(axiosInstance)
import {epics} from '@cxbox-ui/core'
import {combineEpics, createEpicMiddleware} from 'redux-observable'
import {configureStore, getDefaultMiddleware} from '@reduxjs/toolkit'
// Typescript cast if you changed default CXBoxEpic type
const coreEpics = {...epics} as unknown as Record<string, RootEpic>
const rootEpic = combineEpics(Object.values(coreEpics))
const epicMiddleware = createEpicMiddleware({
dependencies: {
api: CXBoxApiInstance
}
})
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware().concat(epicMiddleware)
})
epicMiddleware.run(rootEpic)
Epics
Feel free to add, but be careful to replace or remove some epics. They can call actions that causes chain effect, but if you sure in consequences, manipulate imported epics object as you want.
import {createAction} from '@reduxjs/toolkit'
const sampleAction = createAction<string>('sampleAction')
const sampleEpic: RootEpic = (action$, state$, {api}) =>
action$.pipe(
filter(sampleAction.match),
switchMap(action => {
const name = action.payload
if (name) {
api.doSomething(name)
}
return EMPTY
}),
catchError(error => {
return utils.createApiErrorObservable(error)
})
)
const rootEpic = combineEpics(...Object.values(epics), sampleEpic)
Middlewares
@cxbox-ui/core also exports some middlewares, that not uses Redux-Observable way, but they still need to be applied
import {configureStore, getDefaultMiddleware} from '@reduxjs/toolkit'
import {middlewares} from '@cxbox-ui/core'
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware()
.concat(...Object.values(middlewares))
.concat(epicMiddleware)
})
Widget example
import React from 'react'
import { interfaces } from '@cxbox-ui/core'
import { useDispatch } from 'react-redux'
import { useAppSelector } from '@store'
import { actions } from '@cxbox-ui/core'
import InfoRow from './components/InfoRow'
import { Row } from 'antd'
import { useFlatFormFields } from '@hooks/useFlatFormFields'
interface FunnelProps {
meta: FunnelWidgetMeta
}
function Funnel({ meta }: FunnelProps) {
const { bcName } = meta
const data = useAppSelector(state => state.data[bcName])
const sortedData = data?.slice().sort(sorter)
const funnelData = sortedData?.map(i => ({ id: i.funnelKey, value: i.amount }))
const color = sortedData?.map(i => i.color) as Array<string>
const legend: Types.LegendCfg = {
position: 'right',
layout: 'vertical',
itemMarginBottom: 16,
itemName: { style: { fontSize: 14, fontFamily: 'Roboto', fontWeight: 400 } },
marker: (name, index) => ({
symbol: 'circle',
style: {
fill: color[index]
}
})
}
const label: Label = {
content: labelData => labelData.value,
style: {
fontSize: 20,
fontFamily: 'Roboto',
fontWeight: '700',
fill: '#141F35'
}
}
return (
<div>
<AntFunnel data={funnelData} xField="id" yField="value" color={color} conversionTag={false} legend={legend} label={label} />
</div>
)
}
function sorter(a: interfaces.DataItem, b: interfaces.DataItem) {
return parseInt(b.amount as string) - parseInt(a.amount as string)
}
export default React.memo(Funnel)
Contributing
All contributions are welcomed, as even a minor pull request with grammar fixes or a single documentation update is of a significant help for us!
We promise to handle all PR reviews in a friendly and respectful manner.
Описание
Official mirror of https://github.com/CX-Box/cxbox-ui