diff --git a/README.md b/README.md
index 870408d..9d27561 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,11 @@ import { reducer, epics } from '@flipbyte/redux-datatable';
},
...
},
+ entity: { // optional. Check example code in /demo.
+ state: '{your state path}',
+ responseSchema: // normalizr schema,
+ schema: // normal;izr schema
+ },
layout: [
['Editable'],
['MassActions', 'SimpleButton', 'ResetFilters', 'Spacer', 'Print', 'Columns'],
@@ -316,6 +321,7 @@ const YourComponent = () =>
| rowHeight | integer | true | - | The maximum height of each table body row |
| routes | object | true | - | Routes definition to fetch data and other custom routes config for custom handling (Check below) |
| components | object | true | - | All the components required for your table |
+| entity | object | false | - | [Normalizr](https://github.com/paularmstrong/normalizr) specification. Check below for details. |
| layout | array | true | - | The layout of your table |
| editing | boolean | false | false | Set the default state of the table to be in editing mode |
| primaryKey | string | true | - | Set the primary key column of the table for actions like editing. |
@@ -342,6 +348,18 @@ Please check the example table config object above.
An array of arrays where each inner array represents a row in the layout, within which components can be specified, which will be displayed in the frontend.
Please check the example table config object above.
+#### Entity array
+
+All the fields are required when entity is defined. However, entity key itself is optional in the table config.
+
+| Key | Type | Required | Default | Description |
+| -------------- | ------ | -------- | ------- | --------------------------------------------------------------------------------------- |
+| state | object | true | - | Path to sub state in your top level redux state where the normalized data will be saved |
+| responseSchema | object | true | - | Define how the data is represented in your fetch data api response |
+| schema | object | true | - | Define how the data is represented in each row item of the table fetch repsonse |
+
+Note: Check the [example](https://github.com/flipbyte/redux-datatable/blob/master/demo/src/schema/normalized.js) code.
+
#### Available Components
**_Common Properties_**
diff --git a/demo/src/ExampleTableContainer.js b/demo/src/ExampleTableContainer.js
index c1583cb..77fb1b5 100644
--- a/demo/src/ExampleTableContainer.js
+++ b/demo/src/ExampleTableContainer.js
@@ -3,10 +3,11 @@ import { render } from 'react-dom';
import ReduxDatatable from '../../src';
import config from './config';
-const ExampleTableContainer = ({ title, className, id, ...tableProps }) =>
+const ExampleTableContainer = ({ title, className, id, ...tableProps }) => (
{ title }
+);
export default ExampleTableContainer;
diff --git a/demo/src/config.js b/demo/src/config.js
index 0ea158e..08b6386 100644
--- a/demo/src/config.js
+++ b/demo/src/config.js
@@ -1,9 +1,11 @@
import { reducer, epics } from '../../src';
+import pagesReducer from './reducer';
export default {
tableReducerName: 'reduxDatatable',
reducers: {
- reduxDatatable: reducer
+ reduxDatatable: reducer,
+ pages: pagesReducer,
},
epics: {
pageTableFetchDataEpic: epics.fetchDataEpic,
diff --git a/demo/src/index.js b/demo/src/index.js
index bf01e1c..866199d 100644
--- a/demo/src/index.js
+++ b/demo/src/index.js
@@ -6,7 +6,7 @@ import { Provider } from 'react-redux';
import configureStore from './store';
import config from './config';
-// import '../../node_modules/bootstrap/dist/css/bootstrap.css';
+import '../../node_modules/bootstrap/dist/css/bootstrap.css';
import './css/simple-sidebar.css';
import './css/styles.css';
@@ -17,22 +17,24 @@ const Demo = () => (
- { tables.map(({ id, title }, index) =>
-
{ title } ) }
+ { tables.map(({ id, title }, index) => (
+
{ title }
+ ))}
- { tables.map((table, index) =>
- )}
+ { tables.map((table, index) => (
+
+ ))}
diff --git a/demo/src/reducer.js b/demo/src/reducer.js
new file mode 100644
index 0000000..2d76354
--- /dev/null
+++ b/demo/src/reducer.js
@@ -0,0 +1,22 @@
+import _ from 'lodash';
+import * as actions from '../../src/actions';
+
+export default function reducer(state = {}, action) {
+ if (!action.meta) {
+ return state;
+ }
+
+ const { payload, meta: { name } } = action;
+ const acceptedActions = {
+ [actions.RECEIVE_DATA]: () => ({
+ ...state,
+ ..._.get(payload, ['entities', 'pages'], {})
+ })
+ };
+
+ if (acceptedActions.hasOwnProperty(action.type) && name === 'pages') {
+ return acceptedActions[action.type]();
+ }
+
+ return state;
+}
diff --git a/demo/src/schema/basic.js b/demo/src/schema/basic.js
index 0df46cc..51a1212 100644
--- a/demo/src/schema/basic.js
+++ b/demo/src/schema/basic.js
@@ -226,39 +226,7 @@ export default {
// indexField: '@pageId',
width: 50,
extraData: 'selection',
- }, {
- label: 'ID',
- type: 'number',
- name: 'pageId',
- width: 150,
- filterable: true,
- sortable: true,
- // editable: true
- }, {
- label: 'ID',
- type: 'number',
- name: 'pageId',
- width: 150,
- filterable: true,
- sortable: true,
- // editable: true
- }, {
- label: 'ID',
- type: 'number',
- name: 'pageId',
- width: 150,
- filterable: true,
- sortable: true,
- // editable: true
- }, {
- label: 'ID',
- type: 'number',
- name: 'pageId',
- width: 150,
- filterable: true,
- sortable: true,
- // editable: true
- }, {
+ }, {
label: 'ID',
type: 'number',
name: 'pageId',
diff --git a/demo/src/schema/index.js b/demo/src/schema/index.js
index 63df699..928e110 100644
--- a/demo/src/schema/index.js
+++ b/demo/src/schema/index.js
@@ -1,6 +1,13 @@
import basic from './basic';
+import normalized from './normalized';
export default [
+ {
+ title: 'Normalized Table',
+ id: 'normalized-table',
+ className: 'mb-4',
+ config: normalized
+ },
{
title: 'Basic Table',
id: 'basic-table',
diff --git a/demo/src/schema/normalized.js b/demo/src/schema/normalized.js
new file mode 100644
index 0000000..65921d8
--- /dev/null
+++ b/demo/src/schema/normalized.js
@@ -0,0 +1,336 @@
+import React from 'react';
+import { MODIFY_DATA, REQUEST_DATA, IS_LOADING } from '../../../src/actions';
+import { getItemIds } from '../../../src/utils';
+import { normalize, schema } from 'normalizr';
+
+const tableSchema = ( entityName, idAttributeName, definition = {}) => {
+ const rowSchema = new schema.Entity(entityName, definition, {
+ idAttribute: idAttributeName,
+ });
+
+ const responseSchema = { data: [rowSchema] }
+
+ return {
+ rowSchema: rowSchema,
+ responseSchema: responseSchema
+ }
+};
+
+export default {
+ name: 'pages',
+ height: 400,
+ rowHeight: 50,
+ editing: false,
+ primaryKey: 'pageId',
+ routes: {
+ get: {
+ route: '/page',
+ sort: 'id',
+ dir: 'asc',
+ resultPath: {
+ data: 'data'
+ }
+ },
+ delete: {
+ route: '/users/:id'
+ }
+ },
+ entity: {
+ state: 'pages',
+ responseSchema: tableSchema('pages', 'pageId').responseSchema,
+ schema: tableSchema('pages', 'pageId').rowSchema
+ },
+ layout: [
+ ['Editable'],
+ ['MassActions', 'SimpleButton', 'ResetFilters', 'Spacer', 'Print', 'Columns'],
+ ['Limiter', 'Spacer', 'ResultCount', 'Spacer', 'Pages'],
+ [{ id: 'Table', layout: [
+ ['Header'],
+ ['Filters'],
+ ['Body'],
+ ['Header']
+ ]}],
+ ['Limiter', 'Spacer', 'ResultCount', 'Spacer', 'Pages'],
+ ],
+ components: {
+ // Loader: {
+ // // styles: {
+ // // mask: {
+ // // backgroundColor: 'red',
+ // // },
+ // // spinner: {
+ // // borderTopColor: 'black',
+ // // }
+ // // }
+ // },
+ ResultCount: {
+ styles: {
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center'
+ }
+ },
+ // Pages: {
+ // styles: {
+ // first: { backgroundColor: 'red' },
+ // previous: { backgroundColor: 'green' },
+ // pageNumber: { backgroundColor: 'yellow' },
+ // next: { backgroundColor: 'pink' },
+ // last: { backgroundColor: 'purple' },
+ // }
+ // },
+ Editable: {
+ type: 'editable',
+ labels: {
+ show: 'Make editable',
+ hide: 'Hide editable',
+ save: 'Save',
+ },
+ save: ( config ) => ( dispatch, getState ) => {
+ const tableState = getState()[config.reducerName][config.name];
+ console.log('toolbar save click with modified data', config, tableState.modified);
+ config.action(MODIFY_DATA)({ clear: true });
+ // Dispatch MODIFY_DATA action with clear: true, to reset the modified data
+ // Dispatch REQUEST_DATA action "config.action(REQUEST_DATA)" to refresh data.
+ },
+ // styles: {
+ // show: { backgroundColor: 'blue' },
+ // hide: { backgroundColor: 'black', color: 'white'},
+ // save: { backgroundColor: 'green' }
+ // }
+ // renderer: ( props ) => {}
+ },
+ MassActions: {
+ name: 'actions',
+ label: 'Actions',
+ id: 'dropdown',
+ // styles: {
+ // button: {
+ // backgroundColor: '#aaa'
+ // },
+ // dropdownMenu: {
+ // backgroundColor: 'magento'
+ // },
+ // dropdownItem: {
+ // backgroundColor: 'pink'
+ // }
+ // },
+ options: [{
+ type: 'action',
+ name: 'delete',
+ label: 'Delete',
+ styles: {
+ backgroundColor: 'red',
+ },
+ thunk: ( config ) => ( dispatch, getState ) => {
+ // Get current table state.
+ const tableState = getState()[config.reducerName][config.name];
+ console.log(config, tableState);
+ console.log(getItemIds(tableState.selection, tableState.items, config.primaryKey/*, config.entity.schema*/))
+ confirm('Are your sure you want to delete the selected items?')
+ ? console.log('delete items', config, getState(), tableState)
+ : console.log(false);
+
+ // Filter your selected item ids here for deletion
+ // You can find the selection data in the selection key of the tableState.
+ // When all:true, exclude the ids in the selected object with value false and vice versa.
+ }
+ }, {
+ type: 'action',
+ name: 'edit',
+ label: 'Edit this field',
+ }]
+ },
+ SimpleButton: {
+ type: 'button',
+ label: 'Simple Button',
+ state: false,
+ thunk: ( config ) => ( dispatch, getState ) => {
+ const tableState = getState()[config.reducerName][config.name];
+ console.log('toolbar button click', config, tableState);
+ config.action(REQUEST_DATA)();
+ config.action(IS_LOADING)({ value: true });
+ setTimeout(function() {
+ config.action(IS_LOADING)({ value: false });
+ }, 1000);
+ },
+ // styles: {
+ // backgroundColor: 'green',
+ // color: 'white'
+ // }
+ },
+ ResetFilters: {
+ type: 'reset-filters',
+ label: 'Reset Filters',
+ state: false,
+ // styles: {
+ // backgroundColor: 'red',
+ // color: 'white'
+ // }
+ },
+ Print: {
+ type: 'print',
+ label: 'Print Table',
+ state: false,
+ // styles: {
+ // backgroundColor: 'yellow',
+ // }
+ },
+ Columns: {
+ name: 'columns',
+ type: 'columns',
+ label: 'Columns',
+ visible: true,
+ state: false,
+ // styles: {
+ // button: {
+ // backgroundColor: '#aaa'
+ // },
+ // dropdownMenu: {
+ // backgroundColor: 'magento'
+ // },
+ // dropdownItem: {
+ // backgroundColor: 'pink'
+ // }
+ // }
+ },
+ Limiter: {
+ type: 'limiter',
+ options: [10, 20, 50, 200, 2000, 0],
+ default: 200,
+ // styles: {}
+ },
+ Table: {
+ styles: {
+ // table: {
+ // background: '#000',
+ // },
+ // thead: {
+ // background: '#000'
+ // },
+ // filters: {
+ // background: 'blue'
+ // },
+ // // tbody: {
+ // // background: '#000'
+ // // },
+ // tr: {
+ // header: { fontWeight: 'normal' },
+ // filters: { background: 'green' },
+ // body: { },
+ // },
+ // // th: {
+ // // background: 'red',
+ // // textAlign: 'center',
+ // // ':last-child': {
+ // // textAlign: 'right'
+ // // }
+ // // },
+ // td: {
+ // filters: { backgroundColor: '#000' },
+ // // body: {
+ // // color: '#fff',
+ // // textAlign: 'center',
+ // // ':last-child': {
+ // // textAlign: 'right'
+ // // }
+ // // }
+ // },
+ },
+ columns: [{
+ name: 'ids',
+ label: '',
+ sortable: false,
+ type: 'selection',
+ // indexField: '@pageId',
+ width: 50,
+ extraData: 'selection',
+ }, {
+ label: 'ID',
+ type: 'number',
+ name: 'pageId',
+ width: 150,
+ filterable: true,
+ sortable: true,
+ // editable: true
+ }, {
+ label: "Status",
+ type: "options",
+ name: "entityData.data.status",
+ sortable: true,
+ filterable: true,
+ textAlign: "center",
+ width: 150,
+ options: {
+ "published": {
+ "label": "Published"
+ },
+ "draft": {
+ "label": "Draft"
+ },
+ "unpublished": {
+ "label": "Unpublished"
+ },
+ "pending-review": {
+ "label": "Pending Review"
+ },
+ "trashed": {
+ "label": "Trashed"
+ },
+ "archived": {
+ "label": "Archived"
+ }
+ },
+ editable: true
+ // renderer: ({
+ // data,
+ // colConfig: { name, options }
+ // }) => Not specified
+ }, {
+ label: 'Created at',
+ type: 'date',
+ name: 'createdAt',
+ sortable: true,
+ textAlign: 'left',
+ width: 200,
+ editable: true,
+ filterable: true,
+ }, {
+ label: 'Actions',
+ type: 'actions',
+ name: 'actions',
+ width: 100,
+ items: [{
+ type: 'action',
+ name: 'edit',
+ label: 'Edit',
+ htmlClass: 'btn btn-secondary',
+ params: {
+ id: '@id',
+ },
+ thunk: ( config ) => ( dispatch, getState ) => {
+ console.log('edit', config, getState());
+ }
+ }, {
+ type: 'action',
+ name: 'delete',
+ label: 'Delete',
+ icon: 'trash-alt',
+ params: {
+ id: '@id'
+ },
+ styles: {
+ backgroundColor: 'red',
+ color: 'white'
+ },
+ thunk: ( config ) => ( dispatch, getState ) => {
+ confirm('Are your sure you want to delete this page?')
+ ? console.log('delete', getState())
+ : console.log(false);
+
+ }
+ }]
+ }]
+ }
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 186b89d..e536d1c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@flipbyte/redux-datatable",
- "version": "0.6.1",
+ "version": "0.6.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index e3f2709..54d4d90 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@flipbyte/redux-datatable",
- "version": "0.6.1",
+ "version": "0.6.2",
"description": "React-Redux data table",
"main": "lib/index.js",
"module": "es/index.js",
diff --git a/src/components/Body.js b/src/components/Body.js
index f44c63b..64a696c 100644
--- a/src/components/Body.js
+++ b/src/components/Body.js
@@ -76,6 +76,10 @@ const Body = ({
};
}, []);
+ useEffect(() => {
+ updateTableDimensions();
+ }, [ columns ])
+
return (
resultPath
export const setParamsEpic = ( action$, state$ ) => action$.pipe(
ofType(SET_PAGE, SET_FILTER, SET_SORT, SET_LIMIT),
- concatMap((action) => {
+ mergeMap((action) => {
const { meta: { name, routes, reducerName, entity } } = action;
return of(
@@ -37,7 +37,7 @@ export const setParamsEpic = ( action$, state$ ) => action$.pipe(
export const fetchDataEpic = ( action$, state$, { api }) => action$.pipe(
ofType(REQUEST_DATA),
- switchMap((action) => {
+ mergeMap((action) => {
const { name, routes, entity, reducerName } = action.meta;
const query = _.get(state$.value, [reducerName, name]).query;
@@ -66,7 +66,7 @@ export const fetchDataEpic = ( action$, state$, { api }) => action$.pipe(
)),
takeUntil(action$.pipe(
ofType(REQUEST_DATA_CANCEL),
- filter((cancelAction) => cancelAction.name === name)
+ filter((cancelAction) => cancelAction.meta.name === name)
))
);
})
@@ -74,14 +74,14 @@ export const fetchDataEpic = ( action$, state$, { api }) => action$.pipe(
export const deleteDataEpic = ( action$, state$, { api }) => action$.pipe(
ofType(DELETE_DATA),
- switchMap((action) => {
+ mergeMap((action) => {
const {
meta: { name, routes, reducerName, entity },
payload
} = action;
return api.delete(routes.delete.route, { params: payload.params }).pipe(
- concatMap((response) => {
+ map((response) => {
if(!response.success) {
return of(createNotification({ type: NOTIFICATION_TYPE_ERROR, message: response.result }));
}
diff --git a/src/index.js b/src/index.js
index 42145bf..0f9659d 100755
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,5 @@
export reducer from './reducer';
export * as epics from './epics';
export * as utils from './utils';
-export * as action from './actions';
+export * as actions from './actions';
export { default } from './createTable';