UNPKG

@darwino/darwino-react

Version:

A set of Javascript classes and utilities

542 lines (486 loc) 17.4 kB
/* * (c) Copyright Darwino Inc. 2014-2017. */ import React, { Component } from "react"; import PropTypes from 'prop-types'; import ReactDataGrid, {Row,SimpleCellFormatter} from './react-grid/react-data-grid'; import { DEV_OPTIONS, MobileUtils, EmptyDataFetcher, ArrayDataFetcher, PagingDataFetcher, CursorEntries, Utils} from '@darwino/darwino'; import BaseCursorList from './BaseCursorList'; function filterColumns(columns, stacked) { if(stacked) { return columns.filter( (col) => { if(col.hideWhenStacked) { return false; } return true }) } return columns } export class CursorGridRowRenderer extends React.Component { // Context to read from the parent static contextTypes = { cursorGrid: PropTypes.object }; static propTypes = { idx: PropTypes.number.isRequired }; getGrid() { return this.context.cursorGrid; } isRenderAllRows() { const grid = this.getGrid().refs.reactGrid; return grid && grid.props.renderAllRows; } setScrollLeft(scrollBy) { // if you want freeze columns to work, you need to make sure you implement this as apass through if(this.row) { this.row.setScrollLeft(scrollBy); } } onCellExpand(e) { e.stopPropagation(); var meta = this.props.cellMetaData; if (meta != null && meta.onCellExpand) { meta.onCellExpand({rowData:this.props.row}); } } renderCategory() { const props = this.props; let treeDepth = props.subRowDetails.treeDepth || 0; let marginLeft = treeDepth * 20; const row = props.row; let style = { //height: '30px', border: '1px solid #dddddd', paddingTop: '5px', paddingBottom: '5px', paddingLeft: '5px' }; let onKeyDown = (e) => { const onCellExpand = this.props.cellMetaData.onCellExpand; if (e.key === 'ArrowLeft') { if(row.__meta._expanded) { onCellExpand({rowData:row}); } } if (e.key === 'ArrowRight') { if(!row.__meta._expanded) { onCellExpand({rowData:row}); } } if (e.key === 'Enter') { onCellExpand({rowData:row}); } }; let value = this.formatCategory(row.key); return ( <div onKeyDown={onKeyDown} style={style} tabIndex={0} className="category-cell"> <span className="row-expand-icon" style={{float: 'left', marginLeft: marginLeft, cursor: 'pointer'}} onClick={this.onCellExpand.bind(this)} >{row.__meta._expanded ? String.fromCharCode('9660') : String.fromCharCode('9658')}</span> <strong>{value}</strong> </div> ); } formatCategory(value) { const props = this.props; let treeDepth = props.subRowDetails.treeDepth || 0; const grid = this.getGrid(); let formatter = grid.props.groupBy[treeDepth].formatter if(formatter) { return formatter({value,dependentValues:undefined,row:this.props.row,idx:0,category:true}) } return value; } renderCategoryColumns() { return this.renderRowColumns() } renderTotals() { return this.renderRowColumns() } renderDocument() { return this.renderRowColumns() } renderRowColumns() { if (this.isRenderAllRows()) { return this.renderResponsive(); } else { return (<div><Row ref={ node => this.row = node } {...this.props}/></div>); } } renderResponsive2() { return (<div><Row ref={ node => this.row = node } {...this.props}/></div>); } renderResponsive() { const row = this.props.row; const columns = this.props.columns; let style = { //height: 100, //this.getRowHeight(this.props), //overflow: 'hidden', contain: 'layout' }; return ( <div className="react-grid-Row" style={style} onClick={()=>this.getGrid().handleRowClick(row)}> <div className="react-grid-Cell"> <table> {columns // .filter( (col) => { // return true // }) .map( (col,idx) => { const value = this.getCellValue(col.key) const Formatter = col.formatter const colValue = Utils.isFunction(Formatter) ? (<Formatter value={value} row={row} idx={idx} dependentValues={undefined} reponsive={true}/>) : value //: (<SimpleCellFormatter value={value}/>) if(col.name) { return ( <tr> <td><strong>{col.name}:&nbsp;</strong></td> <td>{colValue}</td> </tr> ) } else { return ( <tr> <td colSpan={2}>{colValue}</td> </tr> ) } }) } </table> </div> </div> ); } getCellValue(key) { let val; if (key === 'select-row') { return this.props.isSelected; } else if (typeof this.props.row.get === 'function') { val = this.props.row.get(key); } else { val = this.props.row[key]; } return val; } render() { // It happens that the row can be empty when the data fetcher is lazily loading them // We check __meta to handle this case, and the row will be rendered empty const row = this.props.row; if(row.__meta) { if(row.__meta.category) { const grid = this.getGrid(); if(grid.props.renderCategoryAsColumns) { return this.renderCategoryColumns(); } return this.renderCategory(); } if(row.__meta.totals) { return this.renderTotals(); } return this.renderDocument(); } return this.renderDocument(); // // React force to return null to not // return null; } } /* * Data Grid diplaying the result of a cursor */ class BaseCursorGrid extends BaseCursorList { // Context to pusblish to the children - documentForm static childContextTypes = { cursorGrid: PropTypes.object }; getChildContext() { return {cursorGrid: this}; } constructor(props) { super(props); this.handleGridSort = this.handleGridSort.bind(this); this.rowGetter = this.rowGetter.bind(this); this.getSubRowDetails = this.getSubRowDetails.bind(this); this.onCellExpand = this.onCellExpand.bind(this); this.handleRowSelected = this.handleRowSelected.bind(this); this.handleRowDeselected = this.handleRowDeselected.bind(this); this.state.selectedIndexes = [] } // // Handling selection // clearSelection() { this.setState({selectedIndexes:[]}) } hasSelectedEntries() { return this.state.selectedIndexes.length>0; } getSelectedEntries() { return this.state.selectedIndexes.map((idx) => { return this.rowGetter(idx) }) } getSelectedDocIds() { return this.state.selectedIndexes.map((idx) => { return this.rowGetter(idx).__meta.id }) } findColumn(key) { const columns = this.props.columns; if(columns) { for(let i=0; i<columns.length; i++) { if(columns[i].key==key) return columns[i] } } return null; } getColumnNames() { let r = [] const columns = this.props.columns; if(columns) { for(let i=0; i<columns.length; i++) { r.push(columns[i].key) } } return r; } getColumnTitles() { let r = [] const columns = this.props.columns; if(columns) { for(let i=0; i<columns.length; i++) { r.push(columns[i].name || columns[i].key) } } return r; } // // Grid data model // createDataFetcher(dataLoader) { if(!dataLoader) return new EmptyDataFetcher(); if(this.props.groupBy || this.props.showResponses || this.props.inMemorySort) { return this.createArrayDataFetcher(dataLoader); } else { return this.createPagingDataFetcher(dataLoader); } } createArrayDataFetcher(dataLoader) { return new ArrayDataFetcher({ dataLoader, onDataLoaded: () => {this._initRows(this.props.expandLevel||0)}, ...this.props.dataFetcher }) } _initRows(expandLevel) { const rawData = this.dataFetcher.getRows() const g = new CursorEntries(rawData) g.inMemorySort = this.props.inMemorySort g.indentDocuments = this.props.indentDocuments; g.processEntries(this.props.processEntries) if(this.sortColumn) { g.sortBy([{column: this.sortColumn, descending:this.sortDescending}]) } else { if(this.props.groupBy) { g.groupBy(this.props.groupBy) } if(this.props.sortBy) { g.sortBy(this.props.sortBy) } } const rows = g.getRows() this.setState( {rows: this._addRows([],rows||[],expandLevel)} ) } _addRows(rows,r,expandLevel) { this.updateSubRowDetails(r); for(let i=0; i<r.length; i++) { const row = r[i] rows.push(row) let expanded = (row.__meta.indentLevel||0)<expandLevel; if(expanded && row.__meta.children) { row.__meta._expanded = true this._addRows(rows,row.__meta.children,expandLevel); } } return rows } createJstoreCursor() { const jsc = super.createJstoreCursor(); if(this.props.showResponses) { jsc.jsonTree(true) } return jsc } rowsCount() { if(this.state.rows) { return this.state.rows.length } return this.dataFetcher ? this.dataFetcher.getRowCount() : 0 } rowGetter(i) { if(i<0) { // Happen when there is a click on the header return null; } if(this.state.rows) { return this.state.rows[i] } return this.dataFetcher ? this.dataFetcher.getRow(i) : null } // // Row selection // handleRowSelected(rows) { this.setState({selectedIndexes: this.state.selectedIndexes.concat(rows.map(r => r.rowIdx))}); } handleRowDeselected(rows) { let rowIndexes = rows.map(r => r.rowIdx); this.setState({selectedIndexes: this.state.selectedIndexes.filter(i => rowIndexes.indexOf(i) === -1 )}); } // // Row expand/toggle // expand(level) { this._initRows(level) } expandAll() { this.expand(9999) } collapseAll() { this.expand(0) } handleGridSort(sortColumn, sortDirection) { let inMemory = this.props.inMemorySort let c = this.findColumn(sortColumn); if(c && sortDirection!="NONE") { this.sortColumn = inMemory ? sortColumn : (c.sortField||sortColumn) this.sortDescending = sortDirection=='DESC' } else { this.sortColumn = null } if(inMemory) { this._initRows(this.props.expandLevel||0) } else { this.reinitData(); } } getSubRowDetails(rowItem) { const cat = rowItem.__meta.category; if(!cat && (!this.props.showResponses && (this.props.groupBy && !this.props.indentDocuments))) { return null } let isExpanded = this._isExpanded(rowItem) const children = rowItem.__meta.children; let treeDepth = rowItem.__meta.indentLevel || 0; if(!cat && this.props.groupBy && !this.props.indentDocuments) { // Also works when the view is sorted, as this will lead to 0 treeDepth = Math.max(0,treeDepth-this.props.groupBy.length); } return { grid: this, // Added to get access to the grid in renderers... group: (cat ||this.props.showResponses) && children && children.length>0, expanded: isExpanded, children: children, field: this.props.expandable || this.props.columns[0].key, // This is field where the expand/collapse arrow appears treeDepth, siblingIndex: rowItem.__meta.siblingIndex, numberSiblings: rowItem.__meta.numberSiblings }; } onCellExpand(args) { let rows = this.state.rows.slice(0); const row = args.rowData let rowIndex = rows.indexOf(row); let subRows = row.__meta.children // Should we use a state here? let expanded = !this._isExpanded(row) row.__meta._expanded = expanded if(subRows) { if (expanded) { this.updateSubRowDetails(subRows); rows.splice(rowIndex + 1, 0, ...subRows); } else { let count = 0; for(let i=rowIndex+1; i<rows.length; i++) { if(rows[i].__meta.indentLevel<=row.__meta.indentLevel) break; count++ rows[i].__meta._expanded = false } rows.splice(rowIndex + 1, count); } } this.setState({ rows }); } updateSubRowDetails(subRows) { subRows.forEach((sr, i) => { sr.__meta.siblingIndex = i; sr.__meta.numberSiblings = subRows.length; }); } _isExpanded(row) { const exp = row.__meta._expanded; if(exp!==undefined) { return exp; } if(this.props.expandLevel) { return (row.__meta.indentLevel||0)<this.props.expandLevel; } return false; } // // Main render method // render() { const props = this.props; const height = this.props.height || 500 const responsive =props.responsive && MobileUtils.isMobile(); const columns = props.columns||(props.grid && props.grid.columns)||[] const rowHeight = this.props.rowHeight; return ( <div style={{height: '100%'}}> {this.renderError && this.renderError()} <ReactDataGrid ref="reactGrid" renderAllRows={responsive} rowGetter={this.rowGetter} rowsCount={this.rowsCount()} minHeight={height} emptyRowsView={this.emptyRow()} onRowClick={(idx,data) => { if(idx>=0) this.handleRowClick(data) }} onRowDoubleClick={(idx,data) => { if(idx>=0) this.handleRowDoubleClick(data) }} rowRenderer={this.props.rowRenderer||CursorGridRowRenderer} onGridSort={this.handleGridSort} { ...((props.groupBy || props.showResponses) && { getSubRowDetails: this.getSubRowDetails, onCellExpand: this.onCellExpand } )} { ...(props.selectRows && !responsive && { rowSelection: { showCheckbox: true, enableShiftSelect: true, onRowsSelected: this.handleRowSelected, onRowsDeselected: this.handleRowDeselected, selectBy: { indexes: this.state.selectedIndexes } } } )} columns={filterColumns(columns,responsive)} rowHeight={rowHeight} /> </div> ); } } export default BaseCursorGrid