UNPKG

ssc-grid

Version:

React grid component for SSC 3.0

677 lines (586 loc) 21.4 kB
'use strict'; exports.__esModule = true; var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _elementType = require('react-prop-types/lib/elementType'); var _elementType2 = _interopRequireDefault(_elementType); var _Table = require('react-bootstrap/lib/Table'); var _Table2 = _interopRequireDefault(_Table); var _Pagination = require('react-bootstrap/lib/Pagination'); var _Pagination2 = _interopRequireDefault(_Pagination); var _GridRow = require('./GridRow'); var _GridRow2 = _interopRequireDefault(_GridRow); var _TextField = require('./TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _sscgridUtils = require('./utils/sscgridUtils'); var _Grid = require('./Grid.actions'); var actions = _interopRequireWildcard(_Grid); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } /** * Grid组件 * * Options: https://datatables.net/reference/option/ * * http://adazzle.github.io/react-data-grid * */ var propTypes = { /** * 当前页面号 */ activePage: _propTypes2['default'].number, /** * 表格模型,表头每一列的名称和类型,比如: * ```js * { * id: 'code', * type: 'string', * label: '编码' * } * ``` * 隐藏列 * ```js * { * hidden: true * } * ``` * 自定义格式化 * ```js * formatter: { * type: 'custom', * callback: value => `前缀_${value}_后缀` * } * ``` */ columnsModel: _propTypes2['default'].oneOfType([_propTypes2['default'].array, // 默认类型应该是数组,但是为了支持mobx传入observable object... _propTypes2['default'].object]).isRequired, /** * 直接映射ReactBootstrap的属性 * http://getbootstrap.com/css/#tables-bordered */ bordered: _propTypes2['default'].bool, /** * 直接映射ReactBootstrap的属性 * http://getbootstrap.com/css/#tables-condensed */ condensed: _propTypes2['default'].bool, /** * 直接映射ReactBootstrap的属性 * http://getbootstrap.com/css/#tables-hover-rows */ hover: _propTypes2['default'].bool, /** * 是否显示搜索框 */ localSearch: _propTypes2['default'].bool, /** * 选择一个单元格 */ onCellChecked: _propTypes2['default'].func, /** * 分页 */ onPagination: _propTypes2['default'].func, /** * A callback fired when the table cell is clicked. * * ```js * function ( * SyntheticEvent event, * number colIdx, * Object columnModel, * number rowIdx, * Object rowObj * ) * ``` * * ```js * (event: Object, colIdx: number, columnModel: Object, rowIdx: number, rowObj: Object) => any * ``` */ onCellClick: _propTypes2['default'].func, /** * 单元格双击事件 * * ```js * function ( * SyntheticEvent event, * number colIdx, * Object columnModel, * number rowIdx, * Object rowObj * ) * ``` */ onCellDoubleClick: _propTypes2['default'].func, /** * 行单击事件 * @param {Event} event * @param {Object} rowObj 行数据对象 */ onRowClick: _propTypes2['default'].func, /** * 行双击事件 * @param {Event} event * @param {Object} rowObj 行数据对象 */ onRowDoubleClick: _propTypes2['default'].func, /** * 当搜索框内容改变的时候 */ onSearchChange: _propTypes2['default'].func, /** * 每一行是否显示操作按钮列 * 默认的操作按钮在最右侧的列中,如果需要指定在左侧,可以通过 * `align`参数来设置 * ``` * { * align: 'left', * className: 'operation', * text: '操作' * } * ``` * 注意:当操作列和选择列同时存在的时候,选择列会显示在操作列的左侧 */ operationColumn: _propTypes2['default'].object, /** * 自定义的操作列组件 * 除非指定了`operationColumn`参数,否则操作列不会显示出来 */ operationColumnClass: _elementType2['default'], /** * 是否显示分页 */ paging: _propTypes2['default'].bool, /** * 行类名,比如 * ```js * ['first-row-class', 'second-row-class'] * ``` */ rowClassName: _propTypes2['default'].arrayOf(_propTypes2['default'].string), /** * 是否启用行选择,复选框/单选框 * 默认为`null`,不显示 * ```js * { * mode: 'checkbox', * onBeforeSelect: () => (), * onSelect: () => (), * onSelectAll: () => () * } * ``` * ### mode选项 * `checkbox`复选,`radio`单选 * ### onBeforeSelect()回调 * 当单行的复选框/单选框的值发生改变的时候触发,如果函数返回非`true`, * 则不执行状态修改,也就是UI上复选框/单选框的选择状态不发生改变,也不执行`onSelect()` * 回调。 * 参数: * - [Number] rowIdx 行index * - [Object] rowObj 行数据 * - [boolean] isSelected 复选框/单选框选中状态true/false * - [Event] event Event对象 * - [Array] selectedRowsObj 当前被选中的行的数据,比如: * ```js * [ { id: '11', name: 'test11', note: 'foo' }, * { id: '22', name: 'test22', note: 'bar' } ] * ``` * ### onSelect()回调 * 当单行的复选框/单选框的值发生改变的时候触发 * 参数同`onBeforeSelect()`回调 * * ### onSelectAll()回调 * ```js * function onSelectAll( * Object tableData, // 所有行的数据 * boolean isSelected, // 复选框/单选框选中状态true/false * Event event, // Event对象 * ) * ``` * 当点击表头复选框值发生改变的时候触发, * 当行被选中的时候,组件会自动往被选中的行添加`selected`类名 */ selectRow: _propTypes2['default'].object, /** * 直接映射ReactBootstrap的属性 * http://getbootstrap.com/css/#tables-striped */ striped: _propTypes2['default'].bool, /** * 表格填充数据 * `type: boolean`,数据类型是 * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Boolean_literals">boolean literal</a>或者是 * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Data_types">Boolean类型</a> * (注意和<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean全局对象</a>区分) * `type: ref`,参照的值比较特殊,是一个object: * ``` * pk_org: { * id: '22EA0EB9-FABA-4224-B290-5D041A1DF773', * code: '0403', * name: '委外部' * } * ``` */ tableData: _propTypes2['default'].oneOfType([_propTypes2['default'].array, // 默认类型应该是数组,但是为了支持mobx传入observable object... _propTypes2['default'].object]).isRequired, /** * 页面数量 */ totalPage: _propTypes2['default'].number, /** * 数据为空数组时 [] ,暂无数据 */ notDataText: _propTypes2['default'].oneOfType([_propTypes2['default'].string, _propTypes2['default'].element]), /** * 表格内部样式名 */ tableClassName: _propTypes2['default'].string }; var defaultProps = { bordered: false, condensed: false, hover: false, operationColumn: null, // 默认不显示操作列 operationColumnClass: 'td', // 默认的操作列必须是<td>组件 paging: false, rowClassName: [], /** * 直接映射ReactBootstrap的属性 */ striped: false, selectRow: null, notDataText: '暂无数据', tableClassName: '' }; var Grid = function (_Component) { (0, _inherits3['default'])(Grid, _Component); function Grid(props) { (0, _classCallCheck3['default'])(this, Grid); var _this = (0, _possibleConstructorReturn3['default'])(this, _Component.call(this, props)); _this.state = { /** * 存储所有行被选中的状态,通过key:value * 其中key就是row index,从0开始 * 注意主结构为Object而不是Array * { * 0: { selected: true }, 第一行被选中 * 1: { selected: false } 第二行未被选中 * } */ selectedRowsObj: {}, /** * 表头行(全选)被选中状态 */ isHeadRowSelected: false }; /** * 初始化表体数据 * props.tableData 用户传入的表体数据 * state.viewedTableData 显示在UI上的表体数据 * 因为Grid组件提供了本地搜索功能,所以props.tableData和state.viewedTableData * 可能是不一样的 */ _this.state.viewedTableData = _this.props.tableData; // 初始化的时候所有行都未被选中 _this.props.tableData.forEach(function (item, index) { _this.state.selectedRowsObj[index] = { selected: false }; }); // Initialize grid with selected rows if (props.selectRow && props.selectRow.selected) { _this.state.selectedRowsObj = (0, _extends3['default'])({}, _this.state.selectedRowsObj, props.selectRow.selected); } // TODO 使用flags和bitmasks来存储行被选中的状态,可以降低操作状态的难度 // 初始化0,相当于00000(对于5行来说) _this.state.selectedFlags = 0; return _this; } Grid.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { // 更新表格体数据 this.setState({ viewedTableData: nextProps.tableData }); // 如果表格数据发生了变化,则清空行选中的状态 // console.log('===', nextProps.tableData === this.state.viewedTableData); // console.log('==', nextProps.tableData == this.state.viewedTableData); if (nextProps.tableData !== this.state.viewedTableData) { this.setState(actions.updateAllRowsSelectedState(false)); this.setState(actions.updateTableHeadRowSelectedState(false)); } }; /** * select row * (call from Ref) * * @param {string} colId Column ID * @param {any} colValue Column value * @memberof Grid */ Grid.prototype.select = function select(colId, colValue, isSelected) { var rowIdx = null; this.state.viewedTableData.forEach(function (row, idx) { if (row[colId] === colValue) { rowIdx = idx; } }); if (rowIdx === null) { console.log('[ssc-grid] select none.'); return; } this.updateCheckboxState(rowIdx, isSelected); }; /** * Update the row checkbox state and update the table header checkbox * according to all rows' checkbox state. * * @param {any} rowIdx row index * @param {any} isSelected checkbox is checked or not * @param {Function} callback called when setState finished * @memberof Grid */ Grid.prototype.updateCheckboxState = function updateCheckboxState(rowIdx, isSelected) { var _this2 = this; var callback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {}; this.setState(actions.updateRowSelectedState(rowIdx, isSelected), function () { _this2.setState(actions.updateTableHeadRowSelectedState((0, _sscgridUtils.isAllRowsSelected)(_this2.state.selectedRowsObj)), callback); }); }; Grid.prototype.handlePagination = function handlePagination(eventKey) { if (this.props.onPagination) { this.props.onPagination(eventKey); } }; /** * 选中一行 * 1. 调用传入的`onBeforeSelect`回调,除非结果为true,否则不修改改行选中状态 * 2. 改变当前行被选中的状态 * 3. 改变表头行(全选)状态 */ Grid.prototype.handleRowSelect = function handleRowSelect(rowIdx, rowObj, isSelected, event) { var _this3 = this; var selectRow = this.props.selectRow; // 记录所有被选中行的数据 var selectedRowsArr = void 0; if (selectRow && selectRow.onBeforeSelect) { selectedRowsArr = this.props.tableData.filter(function (row, idx) { return _this3.state.selectedRowsObj[idx].selected === true; }); if (selectRow.onBeforeSelect(rowIdx, rowObj, isSelected, event, selectedRowsArr) !== true) { return; } } this.updateCheckboxState(rowIdx, isSelected, function () { if (selectRow && selectRow.onSelect) { selectedRowsArr = _this3.props.tableData.filter(function (row, idx) { return _this3.state.selectedRowsObj[idx].selected === true; }); selectRow.onSelect(rowIdx, rowObj, isSelected, event, selectedRowsArr); } }); }; /** * 当选中所有行的时候 * 1. 调用传入的`onBeforeSelectAll`回调,除非结果为true,否则不修改改行选中状态 * 2. 同时改变所有行的选中状态 * 3. 改变表头行(全选)状态 */ Grid.prototype.handleAllRowSelect = function handleAllRowSelect(event) { var selectRow = this.props.selectRow; var isSelected = event.target.checked; if (selectRow && selectRow.onBeforeSelectAll) { if (selectRow.onBeforeSelectAll(this.state.viewedTableData, isSelected, event) !== true) { return; } } // 在状态中选中所有行 this.setState(actions.updateAllRowsSelectedState(isSelected)); // 在状态中选中table head row this.setState(actions.updateTableHeadRowSelectedState(isSelected)); if (selectRow && selectRow.onSelectAll) { selectRow.onSelectAll(this.state.viewedTableData, isSelected, event); } }; Grid.prototype.handleCellChecked = function handleCellChecked(rowIdx, colIdx) { if (this.props.onCellChecked) { this.props.onCellChecked(rowIdx, colIdx); } }; // 搜索文本框内容改变之后,进行重新搜索 Grid.prototype.handleSearchChange = function handleSearchChange(event) { var searchText = event.target.value; this.setState({ viewedTableData: (0, _sscgridUtils.searchFor)(searchText, this.props.tableData) }); }; Grid.prototype.render = function render() { var _this4 = this; var _props = this.props, columnsModel = _props.columnsModel, selectRow = _props.selectRow, operationColumn = _props.operationColumn, CustomComponent = _props.operationColumnClass, notDataText = _props.notDataText, tableClassName = _props.tableClassName; // 直接映射react-bootstrap的属性 var _props2 = this.props, striped = _props2.striped, bordered = _props2.bordered, condensed = _props2.condensed, hover = _props2.hover; var reactBootstrapProps = { striped: striped, bordered: bordered, condensed: condensed, hover: hover }; var selectedRowsObj = this.state.selectedRowsObj; // 列模型不能为空,但是表体数据可以为空。 // 当全部为空,只显示一个空div // 当只有表体数据为空,只显示表头 if (!columnsModel || columnsModel.length === 0) { return _react2['default'].createElement('div', null); } var renderTableHeader = function renderTableHeader() { return columnsModel.map(function (col, key) { var alignClass = ''; // 一般数值型都是金额,所以默认右对齐 // 但是会被column model中的align属性覆盖 if (col.type === 'double') { alignClass = 'text-right'; } // 使用Bootstrap的alignment class来进行对齐 if (col.align) { alignClass = 'text-' + col.align; } // 之前的className只能设定th的class,现在改成使用columnClassName // 可以设定th和td的class // 由于新旧接口共存,所以需要根据优先级设定好 var th = _react2['default'].createElement( 'th', { key: key, className: (0, _classnames2['default'])(alignClass, col.columnClassName, col.className) }, col.label ); return col.hidden === true ? null : th; }); }; var renderCheckboxHeader = function renderCheckboxHeader() { return selectRow ? _react2['default'].createElement( 'th', null, _react2['default'].createElement('input', { type: 'checkbox', checked: _this4.state.isHeadRowSelected, onChange: _this4.handleAllRowSelect.bind(_this4) }) ) : null; }; /** * 渲染操作列的表头 */ var renderOperationHeader = function renderOperationHeader(_ref) { var className = _ref.className, text = _ref.text; return _react2['default'].createElement( 'th', { className: (0, _classnames2['default'])(className) }, text || '操作' ); }; var pagination = _react2['default'].createElement(_Pagination2['default'], { className: 'pagination', prev: true, next: true, first: true, last: true, ellipsis: true, items: this.props.totalPage, maxButtons: 10, activePage: this.props.activePage, onSelect: this.handlePagination.bind(this) }); var headRowClassName = (0, _classnames2['default'])((0, _sscgridUtils.isAllRowsSelected)(selectedRowsObj) && 'selected'); // var onRow = this.props.onRow; return _react2['default'].createElement( 'div', { className: (0, _classnames2['default'])(this.props.className) }, this.props.localSearch ? _react2['default'].createElement(_TextField2['default'], { onChange: this.handleSearchChange.bind(this) }) : null, _react2['default'].createElement( 'div', { className: (0, _classnames2['default'])(tableClassName) }, _react2['default'].createElement( _Table2['default'], reactBootstrapProps, _react2['default'].createElement( 'thead', null, _react2['default'].createElement( 'tr', { className: headRowClassName }, renderCheckboxHeader(), renderTableHeader(), operationColumn ? renderOperationHeader(operationColumn) : null ) ), _react2['default'].createElement( 'tbody', null, this.state.viewedTableData.length === 0 ? _react2['default'].createElement( 'tr', null, _react2['default'].createElement( 'td', { style: { 'textAlign': 'center' }, colSpan: columnsModel.length + (selectRow ? 1 : 0) + (operationColumn ? 1 : 0) }, notDataText ) ) : this.state.viewedTableData.map(function (rowObj, rowIdx) { var selected = false; // 该行是否被选中 if (selectedRowsObj[rowIdx] && selectedRowsObj[rowIdx].selected) { selected = true; } return _react2['default'].createElement(_GridRow2['default'], { key: rowIdx, className: _this4.props.rowClassName[rowIdx], rowIdx: rowIdx, rowObj: rowObj, selectRow: selectRow, selectionMode: selectRow ? selectRow.mode : null, onSelect: selectRow ? _this4.handleRowSelect.bind(_this4, rowIdx, rowObj) : null, selected: selected, operationColumn: operationColumn, operationColumnClass: CustomComponent, columnsModel: columnsModel, onCellChecked: _this4.handleCellChecked.bind(_this4), onCellClick: _this4.props.onCellClick, onCellDoubleClick: _this4.props.onCellDoubleClick, onRowClick: _this4.props.onRowClick, onRowDoubleClick: _this4.props.onRowDoubleClick }); }) ) ) ), this.state.viewedTableData.length > 0 && this.props.paging ? pagination : null ); }; return Grid; }(_react.Component); Grid.displayName = 'Grid'; exports['default'] = Grid; Grid.propTypes = propTypes; Grid.defaultProps = defaultProps; module.exports = exports['default'];