react-ui-datagrid
Version:
React datagrid UI
902 lines (627 loc) • 15.3 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
//import './DataGrid.css';
//import Input from './../Input/Input';
//import DatePicker from './../DatePicker/DatePicker';
//import Select from './../Select/';
import moment from 'moment';
//
class DataGrid extends React.Component {
//
static propTypes = {
readOnly: PropTypes.bool,
hideNull: PropTypes.bool,
scrollBox: PropTypes.bool,
// struct: PropTypes.array.isRequired,
columns: PropTypes.array.isRequired,
rows: PropTypes.array,
rowSelect: PropTypes.bool,
onRowClick: PropTypes.func,
onRowSelect: PropTypes.func,
onRowChange: PropTypes.func,
onDataRequire: PropTypes.func
};
//
static defaultProps = {
readOnly: false,
hideNull: false,
scrollBox: true,
rows: [],
rowSelect: true
};
//
static Formatter = {
//
Null: function(value, opt_hide_null) {
return value === null ? (opt_hide_null ? null : <i>NULL</i>) : String(value);
},
Empty: function(value) {
return value === null ? '-' : String(value);
},
Blank: function(value) {
return '';
},
Date: function(moment) {
return !!moment ? moment.format('DD.MM.YYYY') : '-';
},
DateTime: function(moment) {
return !!moment ? moment.format('DD.MM.YYYY HH:mm:ss') : '-';
}
};
//
static ControlType = {
TEXT: 1,
DATE: 2,
SELECT: 3
};
//
constructor(props, context) {
super(props, context);
// console.info('DataGrid.constructor', props, !!props.rows);
//
this._allowUpdate = true;
// this.props.rows = [];
this._rowClickPrevent = false;
//s
var defaultState = {};
defaultState.offset = 0;
defaultState.selectedIndex = null;
// Input rows
// if (!!props.rows) {
// this._setRows(props.rows, 0);
// }
// Default state
this.state = defaultState;
//
};
//
// componentWillReceiveProps(nextProps) {
// if (!!nextProps.rows) {
// this._clearRows();
// this._setRows(nextProps.rows, 0);
// }
// };
componentWillMount() {
this.load();
};
/**
* Call data require to load data
*/
load() {
if (this.props.onDataRequire) {
this.props.onDataRequire.call(this, this, 0, 1048576, this.props.columns);
}
};
/**
* Reload all data
*/
reload() {
this._clearRows();
this.load();
};
/**
* Build row data from raw row object
* @private
* @param {Object} row
* @return {Object}
*//*
_getValidData(row) {
//console.log('row=', row);
let struct = this.props.struct;
let data = {};
for (let s in struct) {
if (struct.hasOwnProperty(s)) {
//console.log(struct[s]);
let fn = struct[s].type;
let value = row[struct[s].name];
data[struct[s].name] = fn && fn !== Array && !!value ? fn.call(this, value) : value;
}
}
return data;
};
*/
/*
_clearRows() {
this.props.rows = [];
};
clearRows() {
this._clearRows();
this.setState({
offset: 0,
selectedIndex: null
});
// this.updateRows();
};
_addRow(row) {
this.props.rows.push(this._getValidData(row));
};
addRow(row) {
this._addRow(row);
this.updateRows();
};
_insertRow(row, rowIndex) {
this.props.rows.splice(rowIndex, 0, this._getValidData(row));
};
insertRow(row, rowIndex) {
this._insertRow(row, rowIndex);
this.updateRows();
};
_setRow(rowIndex, row) {
//console.log('_setRow #1');
this.props.rows[rowIndex] = this._getValidData(row);
//console.log('_setRow #2');
};
setRow(rowIndex, row) {
this._setRow(rowIndex, row);
this.updateRows();
};
getRow(rowIndex) {
return this.props.rows[rowIndex];
};
_removeRow(rowIndex) {
this.props.rows.splice(rowIndex, 1);
};
removeRow(rowIndex) {
this._removeRow(rowIndex);
if (this._selectedIndex === rowIndex || rowIndex > this.props.rows_length) {
this.setState({selectedIndex: null});
}
// this.updateRows();
};
_putRows(rows, offset) {
//console.log('_putRows', rows, offset);
offset = Number(offset);
for (var i in rows) {
if (rows.hasOwnProperty(i)) {
this._setRow(offset + Number(i), rows[i]);
}
}
};
/**
* Put some rows into existing data array
* @param {array<Object>} rows
* @param {number=} opt_offset
*/
/*
putRows(rows, opt_offset) {
this._putRows(rows, opt_offset || 0);
this.updateRows();
};
_setRows(rows) {
this._clearRows();
this._putRows(rows, 0);
};
setRows(rows) {
this._setRows(rows);
this.updateRows();
};
/**
* @return {Array<Object>}
*/
/* getData() {
return this.props.rows;
};
/**
* @return {Array<Object>}
*/
/* getRows() {
return this.getData();
};
updateRows() {
if (this._allowUpdate) {
this.forceUpdate();
}
};
/**
* @private
* @param {Number} rowIndex
* @return {boolean}
*/
_isSelected(rowIndex) {
return rowIndex === this.state.selectedIndex;
};
/**
* @param {number} rowIndex
* @param {func=} opt_callback
*/
setSelectedIndex(rowIndex, opt_callback) {
this.setState({selectedIndex: rowIndex}, opt_callback);
};
/**
* @return {boolean}
*/
hasSelectedIndex() {
return Number.isInteger(this.state.selectedIndex);
};
/**
* @return {number}
*/
getSelectedIndex() {
return this.state.selectedIndex;
};
/**
* @return {boolean}
*/
hasSelected() {
return this.hasSelectedIndex();
};
/**
* @return {Object|null}
*/
getSelected() {
if (!this.hasSelected()) {
return null;
}
return this.props.rows[this.getSelectedIndex()];
};
/**
* @private
* @param {number} rowIndex
*/
_rowClick(rowIndex, e) {
if (this.props.readOnly) {
return;
}
if (this._rowClickPrevent) {
return;
}
this._rowClickPrevent = true;
setTimeout(() => {
this._rowClickPrevent = false;
}, 400)
//
if (this._isSelected(rowIndex)) {
this.setSelectedIndex(null, () => {
if (this.props.onRowClick) {
this.props.onRowClick.call(this, null, null);
}
});
return;
}
this.setSelectedIndex(rowIndex, () => {
if (this.props.onRowClick) {
this.props.onRowClick.call(this, rowIndex, this.props.rows[rowIndex]);
}
});
};
/**
* @private
* @param {number} rowIndex
*/
_rowDoubleClick(rowIndex, e) {
if (this.props.onRowSelect) {
this.props.onRowSelect.call(this, rowIndex);
}
};
//
_doRowChange(rowIndex, row) {
if (this.props.onRowChange) {
this.props.onRowChange.call(this, rowIndex, row);
}
if (this.props.onChange) {
let rows = this.props.rows;
rows[rowIndex] = row;
this.props.onChange.call(this, rows);
}
};
/**
* @private
* @param {Object} props
* @param {EventTarget} e
* @param {String} value
*/
_textControlChange(rowIndex, name, value) {
//console.log('_textControlChange', rowIndex, name, value);
// let {rowIndex, name} = props;
let row = this.props.rows[rowIndex];
row[name] = value;
this._doRowChange(rowIndex, row);
// var {rowIndex, name, onRowChange} = props;
// this.props.rows[rowIndex][name] = value;
// Update grid
// this.forceUpdate(() => {
// if (onRowChange) {
// onRowChange.call(this, rowIndex, value);
// }
// });
};
/**
* @private
* @param {Object} props
* @param {Moment} moment
*/
_dateControlChange(rowIndex, name, moment) {
// let {rowIndex, name} = props;
let row = this.props.rows[rowIndex];
row[name] = moment;
this._doRowChange(rowIndex, row);
// var {rowIndex, name, onChange} = props;
// this.props.rows[rowIndex][name] = moment;
// Update grid
// this.forceUpdate(() => {
// if (onChange) {
// onChange.call(this, rowIndex, moment);
// }
// });
};
/**
* @private
* @param {Object} props
* @param {sring} key
*/
_selectControlChange(rowIndex, name, key) {
// let {rowIndex, name} = props;
let row = this.props.rows[rowIndex];
row[name] = key;
this._doRowChange(rowIndex, row);
// var {rowIndex, name, onChange} = props;
// this.props.rows[rowIndex][name] = key;
// Update grid
// this.forceUpdate(() => {
// if (onChange) {
// onChange.call(this, rowIndex, key);
// }
// });
};
/**
* Update header offset position
* @private
* @param {EventTarget} e
*/
_scrollEvent(e) {
this.setState({offset: e.target.scrollTop});
};
/**
* Calculate full width of columns
* @private
* @return {number}
*/
_getWidth() {
var width = 0;
var columns = this.props.columns;
for (var i in columns) {
if (columns.hasOwnProperty(i)) {
var c = this.props.columns[i];
width += c.width || 128;
}
}
return width;
};
/**
* Render table header of columns
* @private
* @return {React.Element}
*/
_renderHeaderCols() {
var elements = [];
var columns = this.props.columns;
for (var i in columns) {
if (columns.hasOwnProperty(i)) {
var column = this.props.columns[i];
elements.push([
<div className="px-datagrid-col" style={{width: column.width || 128}} key={i}>{column.title}</div>
]);
}
}
return elements;
};
/**
* Render grid header
* @private
* @return {React.Element}
*/
_renderHeader() {
return (
<div className="px-datagrid-header" style={{top: this.state.offset}}>
<div className="px-datagrid-cols">
{this._renderHeaderCols()}
</div>
</div>
);
};
/**
* Render content control for text input
* @priate
* @param {number} rowIndex
* @param {Object} column
* @param {String} value
* @return {React.Element}
*/
_renderTextControl(rowIndex, column, value) {
/*
const {className, ...props} = column.control;
return (
<div style={{width: column.width}} className={classNames('px-control-text', className)}>
<Input {...props}
value={value}
onChange={this._textControlChange.bind(this, rowIndex, column.name)}
/>
</div>
);
*/
};
/**
* Render content control for date
* @priate
* @param {number} rowIndex
* @param {Object} column
* @param {Moment} value
* @return {React.Element}
*/
_renderDateControl(rowIndex, column, value) {
/*
const {...props} = column.control;
return (
<div style={{width: column.width}} className={classNames('px-control-date', column.control.className)}>
<DatePicker {...props}
locale={column.control.locale || "cs-cz"}
selected={value}
onChange={this._dateControlChange.bind(this, rowIndex, column.name)}
/>
</div>
);
*/
};
/**
* Render content combobox
* @priate
* @param {number} rowIndex
* @param {Object} column
* @param {Moment} value
* @return {React.Element}
*/
_renderSelectControl(rowIndex, column, value) {
/*
const {className, ...props} = column.control;
return (
<div style={{width: column.width}} className={classNames('px-control-select', className)}>
<Select {...props}
value={!!value ? value : ''}
onChange={this._selectControlChange.bind(this, rowIndex, column.name)}
/>
</div>
);
*/
};
/**
* Render classic row cell with formatter
* @priate
* @param {number} rowIndex
* @param {number} colIndex
* @return {React.Element}
*/
_renderCell(rowIndex, colIndex) {
var column = this.props.columns[colIndex];
// Custom cell renderer
if (!!column.renderer) {
var props = {
rowIndex,
colIndex,
row: this.props.rows[rowIndex]
};
//
var element = column.renderer.call(this, props);
// Update element width
return React.cloneElement(element, {
className: classNames('px-custom-renderer', element.props.className),
style: {...element.props.style, width: column.width}
});
}
//
var value = this.props.rows[rowIndex][column.name] || null;
//
// Content controls
if (!!column.control && !this.props.readOnly) {
let control = column.control;
switch (control.type) {
case DataGrid.ControlType.TEXT:
return this._renderTextControl(rowIndex, column, value);
case DataGrid.ControlType.DATE:
return this._renderDateControl(rowIndex, column, value);
case DataGrid.ControlType.SELECT:
return this._renderSelectControl(rowIndex, column, value);
default:
}
}
// Basic renderer
var content = !!column.formatter
? column.formatter.call(this, value)
: DataGrid.Formatter.Null(value, this.props.hideNull);
// : value === null ? (<i>NULL</i>) : String(value);
//
return (
<div style={{width: column.width || 128}}
className={classNames(column.className)}
>
<span>{content}</span>
</div>
);
};
/**
* Render row by index
* @priate
* @param {number} rowIndex
* @return {React.Element}
*/
_renderRow(rowIndex) {
var elements = [];
var columns = this.props.columns;
for (var colIndex in columns) {
if (columns.hasOwnProperty(colIndex)) {
colIndex = Number(colIndex);
elements.push(this._renderCell(rowIndex, colIndex));
}
}
//
const classes = classNames('px-datagrid-row',
this._isSelected(rowIndex) ? 'selected' : null
);
//
let customStyle = !!this.props.customRowStyleRenderer ? this.props.customRowStyleRenderer.call(this, rowIndex) : null;
//
return (
<div className={classes} style={customStyle} key={rowIndex}
onClick={this.props.rowSelect ? this._rowClick.bind(this, rowIndex) : null}
onDoubleClick={this.props.rowSelect ? this._rowDoubleClick.bind(this, rowIndex) : null}
>
{React.Children.map(elements, (element) => {return element})}
</div>
);
};
/**
* Render grid rows
* @private
* @return {React.Element}
*/
_renderRows() {
var elements = [];
for (var colIndex = 0; colIndex < this.props.rows.length; colIndex++) {
elements.push([this._renderRow(colIndex)]);
}
return elements;
};
/**
* @private
* @return {React.Element}
*/
_renderBody() {
return (
<div className="px-datagrid-body">
<div className="px-datagrid-rows">
{this._renderRows()}
</div>
</div>
);
};
/**
*/
render() {
const classes = classNames(
'px-datagrid',
this.props.rowSelect ? 'px-datagrid-row-select' : null
);
// Render without scrollbox container
if (this.props.scrollBox === false) {
return (
<div className={classes} style={{width: this._getWidth()}}>
{this._renderBody()}
{this._renderHeader()}
</div>
);
}
//
return (
<div className={classes}>
<div className="px-datagrid-scrollbox" onScroll={this._scrollEvent.bind(this)}>
<div className="px-datagrid-container" style={{width: this._getWidth()}}>
{this._renderBody()}
{this._renderHeader()}
</div>
</div>
</div>
);
};
};
//
export default DataGrid;