Skip to content

Instantly share code, notes, and snippets.

@zatziky
Last active February 26, 2018 19:06
Show Gist options
  • Save zatziky/72f0826e97a011d3f2af75df84981b61 to your computer and use it in GitHub Desktop.
Save zatziky/72f0826e97a011d3f2af75df84981b61 to your computer and use it in GitHub Desktop.
Our ag-grid redux implementation. It is from our PoC and I wouldn't ever publish such a mess... I am only publishing it because of being asked. :)
import React from 'react';
import {AgGridReact} from 'ag-grid-react';
import * as ColumnUtils from 'common/ag-grid/column/column-utils';
import './myApp.css';
import genericCellEditorFactory from './cellEditors/generic';
export const ACTION_TYPE_SORT = 'sort';
export const ACTION_TYPE_FILTER = 'filter';
/**
* @class {GridComponent}
*/
export default class GridComponent extends React.Component {
// constants
constructor(props) {
super(props);
const self = this;
this.gridComponent = undefined;
this.hasStaticRows = this.props.createDatasource === undefined;
this.gridOptions = {
// https://www.ag-grid.com/javascript-grid-context/index.php
context: {
gridInstance: this
},
rowModelType: this.hasStaticRows ? 'normal' : 'virtual',
rowBuffer: 10, // no need to set this, the default is fine for almost all scenarios
enableServerSideFilter: true,
onAfterFilterChanged: () => this.gridOptions.api.refreshHeader(),
// Displayed rows have changed. Happens following sort, filter or tree expand / collapse events.
onModelUpdated: () => {
// console.log("onModelUpdated");
},
onSelectionChanged: () => {
const selectedNodes = this.gridOptions.api.getSelectedNodes();
self.props.onSelectionChanged(selectedNodes);
},
// Value has changed after editing. params are described under "Callback: New Value Handlers" - https://www.ag-grid.com/javascript-grid-cell-editing/index.php
onCellValueChanged: (params) => {
toggleSorting(self, self.hasStaticRows, false); // It cannot be bound to a field. => setting to false directly
self.gridOptions.enableServerSideFilter = false; // It cannot be bound to a field. => setting to false directly
self.props.onCellValueChanged(params.data, params.node, params);
},
onCellClicked(params){
console.log("onCellClicked", params);
},
onBeforeSortChanged(){
if (self.isDirty) {
const invokingAction = {
action: ACTION_TYPE_SORT,
data: self.api.getSortModel()
};
self.props.showDialogDiscardChanges(invokingAction);
}
},
onBeforeFilterChanged(){
if (self.isDirty) {
const invokingAction = {
action: ACTION_TYPE_FILTER,
data: self.api.getFilterModel()
};
self.props.showDialogDiscardChanges(invokingAction);
}
}
};
toggleSorting(this.gridOptions, this.hasStaticRows, this.props.enableSorting);
}
componentDidUpdate(prevProps, prevState) {
console.log(this.props.rows);
console.log(prevProps.rows);
console.log(prevState);
if (this.props.rows) {
this.gridOptions.api.setRowData(this.props.rows);
this.sizeColumns();
}
}
componentWillReceiveProps(nextProps) {
// console.log('AgGrid - Will receive props', nextProps, this.props);
toggleSorting(this.gridOptions, this.hasStaticRows, !nextProps.isDirty);
this.gridOptions.enableServerSideFilter = !nextProps.isDirty;
if (nextProps.invokingAction.action === ACTION_TYPE_SORT) {
this.props.resetReduxState();
console.log("Reinvoking sort with", nextProps.invokingAction.data);
this.api.setSortModel(nextProps.invokingAction.data);
}
if (nextProps.invokingAction.action === ACTION_TYPE_FILTER) {
this.props.resetReduxState();
this.api.setFilterModel(nextProps.invokingAction.data);
this.api.onFilterChanged();
}
/*
if (nextProps.rows) {
// const model = this.api.getModel();
this.gridOptions.api.setRowData(nextProps.rows);
}
*/
if (nextProps.columnDefs !== this.props.columnDefs) {
let columnDefs = ColumnUtils.makeColumnsReadonly(nextProps.columnDefs, nextProps.isReadOnly);
if (!nextProps.isReadOnly) {
const genericCellEditor = genericCellEditorFactory({
onEditingStart: nextProps.onEditingStart,
onEditingEnd: nextProps.onEditingEnd,
});
columnDefs = ColumnUtils.setCustomCellEditor(columnDefs, genericCellEditor);
}
this.api.setColumnDefs(columnDefs);
}
// resize columns when this prop changes
if (nextProps.sizeColumnsProp !== this.props.sizeColumnsProp) {
// console.log('Grid resize columns with new value:', nextProps.sizeColumnsProp);
this.sizeColumns();
}
}
onGridReady(params) {
this.api = params.api;
this.columnApi = params.columnApi;
if (this.hasStaticRows) {
this.api.setRowData(this.props.rows);
this.sizeColumns();
this.api.hideOverlay();
} else {
this.api.setDatasource(this.props.createDatasource(this));
}
let columnDefs = ColumnUtils.makeColumnsReadonly(this.props.columnDefs,
this.props.isReadOnly);
if (!this.props.isReadOnly) {
const genericCellEditor = genericCellEditorFactory({
onEditingStart: this.props.onEditingStart,
onEditingEnd: this.props.onEditingEnd,
});
columnDefs = ColumnUtils.setCustomCellEditor(columnDefs, genericCellEditor);
}
this.api.setColumnDefs(columnDefs);
//todo - to remove next 3 lines
if (this.props.onGridReady) {
this.props.onGridReady(this);
}
if (this.props.selectFirstRow && (!this.api.getSelectedNodes() || !this.api.getSelectedNodes().length)) {
var model = this.api.getModel();
if (model && model.getRowCount()) {
var node = model.getRow(0);
if (node)
node.setSelected(true, true);
}
}
}
onCellClicked(event) {
// console.log('onCellClicked: ' + event.data.name + ', col ' + event.colIndex);
}
onRowSelected(event) {
if (event.node.selected) {
const params = {
id: event.node.id,
data: event.node.data
};
this.props.onRowSelected(params, event);
}
}
onRowDoubleClicked(event) {
this.props.onRowDoubleClicked(event);
}
getHeight() {
if (this.props.height) {
return this.props.height;
}
if (!this.props.rows || this.props.createDatasource) {
return 400;
}
const height = (this.props.rows.length + 1) * 22 + 24;
if (height < 2 * 22 + 24) return 2 * 22 + 24;
if (height > 400) return 400;
return height + 2;
}
sizeColumns() {
if (this.gridOptions && this.gridOptions.api) {
var api = this.gridOptions.api;
api.doLayout();
setTimeout(function () {
api.sizeColumnsToFit();
}, 200);
}
}
render() {
// console.log('onAGGridRender - props', this.props);
return (
<div style={{height: this.getHeight()}} className="ag-blue">
<AgGridReact
// gridOptions is optional - it's possible to provide
// all values as React props
gridOptions={this.gridOptions}
// listening for events
onGridReady={this.onGridReady.bind(this)}
onRowSelected={this.onRowSelected.bind(this)}
onCellClicked={this.onCellClicked.bind(this)}
onRowDoubleClicked={this.onRowDoubleClicked.bind(this)}
// binding to simple properties
showToolPanel="false"
quickFilterText=""
// binding to an object property
icons={this.props.icons}
rowSelection={this.props.rowSelection || "multiple"}
enableColResize="true"
enableFilter="true"
groupHeaders="true"
rowHeight="22"
debug={this.props.isInDebug}
ref={ref => this.gridComponent = ref}
/>
</div>
);
}
getApi() {
return this.gridOptions.api;
}
}
GridComponent.propTypes = {
isDirty: React.PropTypes.bool,
invokingAction: React.PropTypes.object,
rows: React.PropTypes.array,
columnDefs: React.PropTypes.array,
isReadOnly: React.PropTypes.bool,
isInDebug: React.PropTypes.bool,
enableSorting: React.PropTypes.bool,
selectFirstRow: React.PropTypes.bool,
onRowsChanged: React.PropTypes.func,
onSelectionChanged: React.PropTypes.func,
showDialogDiscardChanges: React.PropTypes.func,
onCellValueChanged: React.PropTypes.func,
removeRow: React.PropTypes.func,
addRow: React.PropTypes.func,
onEditingStart: React.PropTypes.func,
onEditingEnd: React.PropTypes.func,
// callbacks
createDatasource: React.PropTypes.func,
// lifecycle
componentWillReceiveProps: React.PropTypes.func,
resetReduxState: React.PropTypes.func,
sizeColumnsProp: React.PropTypes.number, // utility property for calling grid api
};
function toggleSorting(gridOptions, hasStaticRows, isSortingAllowed) {
const enableOrDeleteKey = (object, key, isSortingAllowsd) => {
if (isSortingAllowsd) {
object[key] = true;
} else {
delete object[key];
}
};
// Set one of these to true to enable sorting.
// `enableSorting` will allow header clicks and show sort icons and sort within the grid.
// `enableServerSideSorting` will allow header clicks and show sort icons, but the sorting will be deferred to your datasource.
// https://www.ag-grid.com/javascript-grid-properties/index.php
if (hasStaticRows) {
enableOrDeleteKey(gridOptions, 'enableSorting', isSortingAllowed);
} else {
enableOrDeleteKey(gridOptions, 'enableServerSideSorting', isSortingAllowed);
}
}
GridComponent.defaultProps = {
enableSorting: false,
selectFirstRow: false,
icons: {
columnRemoveFromGroup: '<i class="fa fa-remove"/>',
filter: '<i class="fa fa-filter"/>',
sortAscending: '<i class="fa fa-long-arrow-down"/>',
sortDescending: '<i class="fa fa-long-arrow-up"/>',
groupExpanded: '<i class="fa fa-minus-square-o"/>',
groupContracted: '<i class="fa fa-plus-square-o"/>',
columnGroupOpened: '<i class="fa fa-minus-square-o"/>',
columnGroupClosed: '<i class="fa fa-plus-square-o"/>'
},
columnDefs: [],
invokingAction: {},
onRowsChanged: () => {
},
showDialogDiscardChanges: () => {
},
onCellValueChanged: () => {
},
onGridReady() {
},
onRowSelected() {
},
onSelectionChanged() {
},
onStartCellEditing() {
},
onRowDoubleClicked() {
},
onStopCellEditing() {
},
onEditingStart() {
},
onEditingEnd() {
}
};
import * as ReactRedux from 'react-redux';
import * as actions from './actions';
import GridComponent from 'common/component/Grid/Grid.component';
import RowsDatasource from './component/RowsDatasource';
import {selectorGridSwitchPorts} from 'root-reducer.js';
const mapStateToProps = (state) => {
const grid = selectorGridSwitchPorts(state);
return {
columnDefs: grid.columns,
isReadOnly: grid.isReadOnly,
enableSorting: grid.enableSorting,
}
};
const mapDispatchToProps = (dispatch) => {
return {
onRowsChanged(ports, isModelCleared){
dispatch(actions.storeRows(ports, isModelCleared))
},
createDatasource(gridComponent){
return new RowsDatasource(
gridComponent,
data => actions.pageDataReceived(data)
);
},
resetReduxState(){
dispatch(actions.resetReduxState());
}
}
};
export default ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(GridComponent)
import portService from 'service/port/ports.service';
class RowsDatasource {
/**
* @param gridComponent.api
* @param gridComponent.gridOptions
*/
constructor(gridComponent){
this.getRows = this.getRows.bind(this, gridComponent);
}
/** https://www.ag-grid.com/angular-grid-datasource/index.php */
/**
* @param params.startRow: The first row index to get.
* @param params.endRow: The first row index to NOT get.
* @param params.successCallback: Callback to call for the result when successful.
* @param params.failCallback: Callback to call for the result when failed.
* @param params.context: The grid context object.
*/
getRows(gridComponent, params){
console.log("GETROWS - STARTROW: ", params.startRow);
const query = {
startRow: params.startRow,
endRow: params.endRow,
sortModel: params.sortModel,
filterModel: params.filterModel
};
// console.log("Getting rows for ", query);
portService.getPortsForAgGrid(query)
.then(response => response.data)
.then(ports => {
const isModelCleared = params.startRow === 0;
if (isModelCleared) {
gridComponent.props.resetReduxState();
}
gridComponent.props.onRowsChanged(ports);
const lastRow = _calculateLastRow(params.startRow, params.endRow, ports);
params.successCallback(ports, lastRow);
});
}
}
function _calculateLastRow(startRow, endRow, ports) {
if (ports.length < endRow - startRow) {
return startRow + ports.length;
}
return -1;
}
export default RowsDatasource;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment