A Grid Component written in React using the Redux Pattern.
- Local and/or Remote Data Source
- Local and/or Remote Pagination
- Extensive and extenable Column Definitions
- Draggable Column Width/Resizing
- Draggable Column Ordering
- Sortable Columns
- Grid Action Menus
- Bulk Action Toolbar
- Selection Model (Single, MultiSelect, Checkbox)
- Event Handling for all kinds of DOM Events (List Below)
- Extendable and Modular Style Built with JavaScript
- Loading Mask
- Built-in Error Handling Module
Add Grid to node_modules
npm install react-redux-grid
Include Grid in Webpack Build via Loaders
var loaders = [
{
test: /\.js$|\.jsx$/,
loaders: ['react-hot', 'babel-loader'],
exclude: /[\\\/]node_modules[\\\/](?!react-redux-grid)/,
},
{
test: /\.styl$/,
exclude: /[\\\/]node_modules[\\\/](?!react-redux-grid)/,
loaders: ['style-loader', 'css-loader', 'stylus-loader']
}
];
Because the grid gets installed in the node_modules
directory, we must add the specific folder to the exclude
config entry in webpack. This way, when you import
the grid, webpack will know which loader to use to parse the files.
If you're using the fonts that come bundles with the grid, you will also need an additonal loader:
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loaders: ['url-loader?limit=10000&mimetype=application/font-woff' ]
}
import { Grid, Store } from 'react-redux-grid'
const gridData = {
store: Store,
...data
};
const GridInstance = <Grid { ...gridData } />
Because Redux imposes a single store pattern, it's very probable that you will need to use this grid with your own store. Well that's ok! The demo
folder includes:
index.html
entry.js
The entry point for webpack to build all required assetsprovider.jsx
The Redux entry point, where your store will be declared
This component will work out of the box, without installing a custom store. A store has been created within the components source files, but this store will be entirely ignored if a different store is passed to the grid.
In the demo provider.jsx
, we are simply passing the store at path'../src/store/store';
. To use your own store, simply provide your own store (recommended) or overwrite the source store.
If you elect to use a custom store, the core component's reducers are also exposed from within the core export:
const Reducers = {
DataSource: datasource,
Grid: grid,
BulkActions: bulkaction,
Editor: editor,
ErrorHandler: errorhandler,
Filter: filter,
Loader: loader,
Menu: menu,
Pager: pager,
Selection: selection
};
You can import a reducer like so:
import { Reducers } from 'react-redux-grid';
const dataSource = Reducers.DataSource;
const rootReducer = combineReducers({
dataSource,
yourCustomReducer
};
Data can be provided to a grid in two ways.
- As a Local Data Source (in memory)
- As a Remote Data Source, via AJAX (by providing a route, or a function which returns a
Promise
)
If data is provided as a local variable it should be described as an array of objects, and declared using data = [];
.
If data is provided as a request route, it should be described as a string or a function and declared using dataSource = path/to/data/source;
.
If data is provided as a function, it should return a Promise
Examples of both cases:
const data = [
{
name: 'Michael Jordan',
position: 'Shooting Guard'
},
{
name: 'Charles Barkley',
position: 'Power Forward'
}, ...
];
const grid = <Grid data={ data } />
const dataSource = 'route/to/your/data/source';
const grid = <Grid dataSource={ dataSource } />
const dataSource = () => {
return new Promise((success) =>{
return $.get({
method: 'GET',
route: '/route/to/your/data',
}).then((response) => {
// important to note, that the grid expects a data attribute
// on the response object!
const gridResponse = {
data: response.items,
success: true
};
success(gridResponse);
});
});
};
const grid = <Grid dataSource= { dataSource } />
All Features are implemented as plugins. Plugin defaults are described as below. Their default values can be modified, and new plugins can be introduced with very little modification to core components.
To add a plugin, simply create a file, and add the JSX to src/grid.jsx
. The plugin will not interfere with other components or their associated state.
export const columns = [
{
name: 'Name',
dataIndex: 'name',
editor: '<input type="text" required />',
width: '10%',
className: 'additional-class',
renderer: ({ column, value, row }) => {
return (
<span> Name: { value } </span>
);
},
hidden: false,
placeholder: 'Name',
validator: (val) => {
return val.length > 0;
},
hideable: false,
resizable: false,
moveable: false,
HANDLE_CLICK: () => { console.log('Header Click'); }
}
];
Column Defaults:
name
:string
, title of columndataIndex
:string
, the key accessor for the column value (required parameter)editor
:jsx element
, when an editor is used, this element will be rendered in place of the edited cellwidth
:string | int
, width of column (if none is provided, a default width will be applied)className
:string
, additional class names to apply to columns header outputrenderer
:string | jsx
, a function which returns the cell contents for this columnhidden
:boolean
, whether the column is hidden or visiblehideable
:boolean
, whether the column can be hiddenresizable
:boolean
, whether this column can be resizedmoveable
:boolean
, whether this column can be movedplaceholder
:string
, the placeholder that will be used for the editor inputvalidator
:func
, a func that should return a boolean, to determine if the newly input value is validHANDLE_CLICK
:func
, click handler assigned to column
export const plugins = {
EDITOR: {
type: 'inline',
enabled: true,
focusOnEdit: true
}
}
Editor Defaults
type
:string
, (inline
), currently there is only 1 editor defined, in the future, other editors will be availableenabled
:boolean
, if true, an editor will be renderedfocusOnEdit
:boolean
, focus the first editable input when an edit event occurs (defaults to true)
export const plugins = {
COLUMN_MANAGER: {
resizable: false
defaultColumnWidth: `${100 / columns.length}%`,
minColumnWidth: 10,
moveable: true,
headerActionItemBuilder: () => {},
sortable: {
enabled: true,
method: 'local',
sortingSource: 'http://url/to/sortingSource'
}
}
}
Column Manager Defaults
resizable
:boolean
, will set all columns to resizable. This parameter will not override columns that have declared they are not resizable from the columns arraydefaultColumnWidth
:string template
, if no column width is provided, columns will be divided equally. this can be overwritten by providing a new string templateminColumnWidth
:int
, the minimum width a column can be dragged tomoveable
:boolean
, whether the columns can be reordered by dragheaderActionItemBuilder
:func
, build a custom jsx component to be used as the header action itemssortable
:object
, an object that describes whether columns can be sorted 6a.sortable.enabled
:boolean
, whether sorting is enabled at the grid level 6b.sortable.method
:string
, whether sorting will execute locally, or remotely, possible values:['local', 'remote']
6c.sortable.sortingSource
:string
, where sorting data will be retrieved (a required parameter for remote sorting)
export const plugins = {
PAGER: {
enabled: true,
pagingType: 'remote',
pagingSource: 'url/to/grid/data', // not required, will default to dataSource
toolbarRenderer: (pageIndex, pageSize, total, currentRecords, recordType) => {
return `${pageIndex * pageSize} through ${pageIndex * pageSize + currentRecords} of ${total} ${recordType} Displayed`;
},
pagerComponent: false
}
};
Pagination Defaults:
enabled
:boolean
, default value:true
pagingType
:string
, default value;local
, possible values:['local', 'remote']
pagingSource
:string
, url for remote grid data (ifpagingSource
is null, the toolbar will default to the original dataSource passed to grid)toolbarRenderer
:func
, a function which returns the template that describes current pagination statepagerComponent
:jsx element
, if you'd like to pass your own pager in, you can supply a jsx element which will replace the pager entirely
You can pass a JSX
element to replace the pager entirely. This can be either a connected or unconnected component. To dispatch paging events, you can use the Actions.PagerActions.setPageIndexAsync
or Actions.PagerActions.setPage
that are exposed from the main export.
Example:
// my custom actionHandler that my custom component uses
export const setPageIndex = (pageIndex, pageSize, dataSource) => {
return (dispatch) => {
// dispatch an event that the grid is listening for, which updates the current records
dispatch(
Actions.PagerActions
.setPageIndexAsync(pageIndex, pageSize, dataSource)
);
// dispatch an event that my custom toolbar is listening for
// to update the current pageIndex
dispatch({
type: SET_PAGE_INDEX,
pageIndex
});
};
};
export const plugins = {
SELECTION_MODEL: {
mode: 'single',
enabled: true,
editEvent: 'singleclick',
allowDeselect: true,
activeCls: 'active-class',
selectionEvent: 'singleclick'
}
};
Selection Model Defaults:
mode
:string
, determines whether a single value, or multiple values can be selected, and whether a checkbox column will be used, possible values['single', 'multi', 'checkbox-single', 'checkbox-multi']
editEvent
:string
, (singleclick
,doubleclick
,none
) what type of mouse event will trigger the editorenabled
:boolean
, whether the selection model class is initializedallowDeselect
:boolean
, whether a value can be deselectedactiveCls
:string
, the class applied to active rows upon selectionselectionEvent
:string
, the browser event which triggers the selection event, possible values['singleclick', 'doubleclick']
export const plugins = {
ERROR_HANDLER: {
defaultErrorMessage: 'AN ERROR OCURRED',
enabled: true
}
};
export const plugins = {
FILTER_CONTAINER: {
enabled: true,
method: 'local',
filterSource: 'http://url/to/pagingdata',
component: false,
enableFilterMenu: true
fields: [
{
name: 'name',
label: 'Name',
placeholder: 'Name',
type: 'text'
}
]
}
};
Filter Container and Menu Defaults
enabled
:string
, whether the container should be usedmethod
:string
, what type of filter method should be used, possible values:['local', 'remote']
filterSource
,string
, if remote filtering is used, a URI is requiredenableFilterMenu
,boolean
, whether a filter menu should be createdcomponent
,jsx
, if you'd like to add a custom component on top of the grid, provide a jsx componentfields
:array of objects
, an array containing objects that desribe filter fields to be registered in the filter menu 6a.field.object
,object
, fields require aname
parameter, and the name should correspond with a column name.
Error Handler Defaults
defaultErrorMessage
:string
, the default error message to display when no error information is availableenabled
:string
, whether the error handler should be used
export const plugins = {
LOADER: {
enabled: true
}
};
Loader Defaults:
enabled
:boolean
, whether the default loading mask should be used
Loader css can be modified or overwritten using src/style/components/plugins/loader/loadingbar.styl
export const plugins = {
BULK_ACTIONS: {
enabled: true,
actions: [
{
text: 'Bulk Action Button',
EVENT_HANDLER: () => {
console.log('Doing a bulk action');
}
}
]
}
};
Bulk Action Defaults
enabled
:string
, whether the bulk action toolbar should be usedactions
:array of objects
, the actions (including button text, and event handler) that will be displayed in the bar
All grid events are passed in as a single object.
export const events = {
HANDLE_CELL_CLICK: (cell, reactEvent, id, browserEvent) => {
console.log('On Cell Click Event');
},
HANDLE_CELL_DOUBLE_CLICK: (cell, reactEvent, id, browserEvent) => {
console.log('On Cell Double Click Event');
},
HANDLE_ROW_CLICK: (row, reactEvent, id, browserEvent) => {
console.log('On Row Click Event');
},
HANDLE_ROW_DOUBLE_CLICK: (row, reactEvent, id, browserEvent) => {
console.log('On Row Double Click Event');
},
HANDLE_BEFORE_SELECTION: () => {
console.log('On Before Selection')
},
HANDLE_AFTER_SELECTION: () => {
console.log('On After Selection');
},
HANDLE_AFTER_INLINE_EDITOR_SAVE: () => {
console.log('On After Save Inline Editor Event');
},
HANDLE_BEFORE_BULKACTION_SHOW: () => {
console.log('On Before Bulk Action Show');
},
HANDLE_AFTER_BULKACTION_SHOW: () => {
console.log('On After Bulk Action Show');
}
};
All core components and plugins have corresponding .styl
files that can be extended or overwritten. Class names have also been modularized and are available to modify or extend within src/constants/gridConstants.js
export const CSS_PREFIX = 'react-grid';
export const CLASS_NAMES = {
ACTIVE_CLASS: 'active',
DRAG_HANDLE: 'drag-handle',
SORT_HANDLE: 'sort-handle',
SECONDARY_CLASS: 'secondary',
CONTAINER: 'container',
TABLE: 'table',
HEADER: 'header',
ROW: 'row',
CELL: 'cell',
PAGERTOOLBAR: 'pager-toolbar',
EMPTY_ROW: 'empty-row',
LOADING_BAR: 'loading-bar',
DRAGGABLE_COLUMN: 'draggable-column',
COLUMN: 'column',
SORT_HANDLE_VISIBLE: 'sort-handle-visible',
BUTTONS: {
PAGER: 'page-buttons'
},
SELECTION_MODEL: {
CHECKBOX: 'checkbox',
CHECKBOX_CONTAINER: 'checkbox-container'
},
ERROR_HANDLER: {
CONTAINER: 'error-container',
MESSAGE: 'error-message'
},
EDITOR: {
INLINE: {
CONTAINER: 'inline-editor',
SHOWN: 'shown',
HIDDEN: 'hidden',
SAVE_BUTTON: 'save-button',
CANCEL_BUTTON: 'cancel-button',
BUTTON_CONTAINER: 'button-container'
}
},
GRID_ACTIONS: {
CONTAINER: 'action-container',
SELECTED_CLASS: 'action-menu-selected',
MENU: {
CONTAINER: 'action-menu-container',
ITEM: 'action-menu-item'
}
},
FILTER_CONTAINER: {
CONTAINER: 'filter-container',
INPUT: 'filter-input',
SEARCH_BUTTON: 'filter-search-button',
MENU_BUTTON: 'filter-menu-button',
CLEAR_BUTTON: 'filter-clear-button',
BUTTON_CONTAINER: 'filter-button-container',
MENU: {
CONTAINER: 'advanced-filter-menu-container',
TITLE: 'advanced-filter-menu-title',
BUTTON: 'advanced-filter-menu-button',
BUTTON_CONTAINER: 'advanced-filter-menu-button-container',
FIELD: {
CONTAINER: 'advanced-filter-menu-field-container',
LABEL: 'advanced-filter-menu-field-label',
INPUT: 'advanced-filter-menu-field-input'
}
}
},
BULK_ACTIONS: {
CONTAINER: 'bulkaction-container',
DESCRIPTION: 'bulkaction-description',
SHOWN: 'shown',
HIDDEN: 'hidden'
}
};
This is possible. You simply need to pass the reducer name as a prop
.
const gridProps = {
reducerKeys: {
grid: 'YOUR_GRID_REDUCER_KEY'
},
store
};
const grid = <Grid { ...gridProps } />;
I see fonts are included in the package. Do I have to build them with my webpack? Do I have to use them at all?
The fonts will be used by default. If you'd like to remove them from the build process entirely (and thus remove them from the Grid), set a stylus variable!
You can do this via JS, stylus, or webpack:
const stylus = import('stylus');
stylus.define('$include_fonts', false);
$include_fonts = false
variables.styl
$include_fonts = false
stylus: {
'import': [
path.join(__dirname, 'build/variables.styl')
]
}