////////////////////////////////////////////////////////////// // Copyright (c) 2017 Ben Jackman // All Rights Reserved // please contact ben@jackman.biz // for licensing inquiries ////////////////////////////////////////////////////////////// //MIT license granted to ESGI //Quickly make a table with bells and whistles // import { Grid, Data, Formatters } from 'slickgrid-es6'; // require('slickgrid-es6') //console.log(slickGrid as Slick.SlickData) import * as React from "react" import * as _ from "lodash" import {CreateSlickGrid, CreateSlickAutoTootips} from "./SlickWrap" import {CreateSlickDataView} from "./SlickWrap" import * as Papa from "papaparse" import {HeaderFilters} from "./HeaderFilter" import {Cls} from "h-cls" import {Global} from "h-ts" export type QIDS = {id: any; idx: number} interface QColProps { getFilterValue?(item: T): {} | null } export type QCol = QColProps & Slick.Column export type QTableProps = { cols: QCol[] rows: T[] ready?: (qtable: QTable) => void className?: string } // type Props = React.HTMLProps & QTableProps type Props = QTableProps export class TableFilters { filterFn = (item: T, args: any): boolean => true // updated() { // this.dataview.refresh() // } } export class QTable extends React.Component, {}> { tableEl = React.createRef() dataView: Slick.Data.DataView = CreateSlickDataView() headerFilters = new HeaderFilters(this.dataView) tableFilters = new TableFilters() grid!: Slick.Grid constructor(props: Props) { super(props) } enableAutoTooltips() { this.grid.registerPlugin(CreateSlickAutoTootips()) } componentWillReceiveProps( nextProps: Readonly>, nextContext: any, ): void { //Todo do this in a much better way, right now this will probably just //end up splashing items at the end of the list-o-reeno //Allow the initial update of rows if (this.props.rows.length == 0) { this.dataView.beginUpdate() nextProps.rows.forEach(row => this.dataView.addItem(row)) this.dataView.endUpdate() this.dataView.refresh() } // this.dataView.beginUpdate() // this.dataView.getItems().length = 0 // this.dataView.endUpdate() // this.dataView.beginUpdate() // nextProps.rows.forEach(row => this.dataView.addItem(row)) // this.dataView.endUpdate() // this.grid.invalidateAllRows() // this.grid.render() //Someday i'll make this work (Sheyal right!) } refresh() { this.dataView.beginUpdate() this.dataView.getItems().length = 0 this.props.rows.forEach(row => this.dataView.addItem(row)) this.dataView.endUpdate() this.dataView.refresh() this.grid.invalidateAllRows() this.grid.render() } getVisibleItems(): T[] { const grid = this.grid //Get the headers as an array return _.range(grid.getDataLength()).map(i => grid.getDataItem(i)) } getCsv(opts: {fieldToHeaderName?: (field: string) => string}): string { const grid = this.grid const cols = grid.getColumns().map(c => c.field!) const headers = opts.fieldToHeaderName != null ? cols.map(f => opts.fieldToHeaderName!(f)) : cols const items = this.getVisibleItems() const csvHdr = Papa.unparse([headers]) const csvBody = Papa.unparse( { fields: cols, data: items, }, { header: false, quotes: false, delimiter: ",", newline: "\n", } as any, ) return csvHdr.trim() + "\n" + csvBody } getByRowCell = (args: { row: number cell: number }): {field?: string; item?: T; value?: any} => { const item = this.dataView.getItem(args.row) if (item == null) { return {} } else { const field = this.grid.getColumns()[args.cell].field if (field != null) { const value = (item as any)[field] return {field, item, value} } else { return {item} } } } resizeHandler = () => { setTimeout(() => { this.grid.resizeCanvas() }, 0) // console.log("RESIZE CANVAS0") // _.throttle(() => { // console.log("RESIZE CANVAS1") // // }, 1000); } clearSort = () => { this.grid.setSortColumns([]) this.dataView.sort((a: T, b: T) => a.idx - b.idx, true) } moveActiveCellBy = (by: {row?: number; col?: number}) => { const r = by.row || 0 const c = by.col || 0 const {row, cell} = this.grid.getActiveCell() this.grid.setActiveCell(row + r, cell + c) } moveActiveCellTo = (to: {row?: number; col?: number}) => { const {row: r, cell: c} = this.grid.getActiveCell() const row = to.row == null ? r : to.row const cell = to.col == null ? r : to.col this.grid.setActiveCell(row, cell) } componentDidMount() { const dv = this.dataView const cols = this.props.cols const grid = (this.grid = CreateSlickGrid( this.tableEl.current!, this.dataView, cols, { showHeaderRow: true, editable: true, leaveSpaceForNewRows: true, enableCellNavigation: true, enableColumnReorder: false, explicitInitialization: true, multiColumnSort: true, autoEdit: false, // forceFitColumns: true, }, )) Global.instance.DEBUG_GRID = grid dv.onRowCountChanged.subscribe(() => { grid.updateRowCount() grid.render() }) dv.onRowsChanged.subscribe((e, args) => { grid.invalidateRows(args.rows) grid.render() }) const filterFn = (item: T, args: any): boolean => { return ( this.tableFilters.filterFn(item, args) && this.headerFilters.filterFn(item, args) ) } //set the dv.setFilter(filterFn) //Header filters grid.onHeaderRowCellRendered.subscribe((e, args) => { const myNode = args.node while (myNode.firstChild) { myNode.removeChild(myNode.firstChild) } const filter = this.headerFilters.createAndAdd(args.column) args.node.appendChild(filter.el) }) grid.onColumnsResized.subscribe((e, args) => { console.log(grid.getColumns().map(c => c.width)) }) //Sorting grid.onSort.subscribe((e, args) => { console.log("ON SORT CALLED") const sortCols = args.sortCols && args.sortCols.length > 0 ? args.sortCols : args.sortCol ? [ { sortCol: args.sortCol, sortAsc: args.sortAsc, }, ] : null if (sortCols != null) { ;(dv as any).sort((a: any, b: any) => { let i = 0 let ret = 0 while (i < sortCols.length && ret == 0) { let col = sortCols[i] let sign = sortCols[i].sortAsc ? 1 : -1 let a1 = a[col.sortCol.field!] let b1 = b[col.sortCol.field!] const res = a1 - b1 if (_.isNaN(res)) { const a2 = (a1 || "").toLowerCase() const b2 = (b1 || "").toLowerCase() const cmp = a2.localeCompare(b2) if (cmp != 0) { return cmp * sign } } else { return sign * res } i += 1 } return ret }) } else { //Clear any existing sort console.log("Clearing sort") dv.sort((a: any, b: any) => a.idx - b.idx, true) } grid.invalidate() grid.render() }) grid.init() setTimeout(() => { grid.resizeCanvas() }, 0) dv.beginUpdate() this.props.rows.forEach(row => dv.addItem(row)) dv.endUpdate() dv.refresh() window.addEventListener("resize", this.resizeHandler) if (this.props.ready) { this.props.ready(this) } } componentWillUnmount() { window.removeEventListener("resize", this.resizeHandler) } render() { return (
) } } //COMMON PATTERNS //-------------- // Clicking //---- // grid.onClick.subscribe((e: DOMEvent, args) => { // const item = dv.getItem(args.row) // const key = grid.getColumns()[args.cell].field! // // if (e.shiftKey) { // // refreshPane.refreshContainer(item.containerId) // // } // // console.log("CLICKEROO", e, args, item, key) // if (this.props.clicked) { // this.props.clicked(e as MouseEvent, item, key) // } // // }) // MOUSE OVER //---- //Add mouse over logic // table.grid.onMouseEnter.subscribe((e: MouseEvent, args) => { // const cell = table.grid.getCellFromEvent(e) // if (cell != null) { // const item = table.dataView.getItem(cell.row) // if (this.refTooltip) { // console.log("Entered cell", cell, item) // // this.refTooltip.show(e.clientX, e.clientY) // } // } // }) // table.grid.onMouseLeave.subscribe((e, args) => { // console.log("Left cell") // if (this.refTooltip) { // this.refTooltip.hide() // } // // itemDetailHover.hide() // }) // export abstract class QData { // static forArray(rows: Array, // colnames: Array, // valueFn: (cname: string, cidx: number, r: R) => string): QData { // return new class QDataForArray extends QData { // get rowCnt(): number { // return rows.length; // } // // get colCnt(): number { // return colnames.length // } // // getColname(ci: number): string { // return colnames[ci] // } // // getRC(ri: number, ci: number): string { // return valueFn(colnames[ci], ci, rows[ri]) // } // } // } // // abstract getColname(c: number): any // // abstract getRC(r: number, c: number): string // // abstract get rowCnt(): number // // abstract get colCnt(): number // // // } // class SlickHelp { // // getTableInfo(colnames: ColInfo[], rows: Array) { // // } // // createSlickColumns(): Slick.Column[] { // function makeColumn(name: string, tooltip: string, width: number = -1, f: (row: any) => any) { // return { // id: name, // name: name, // field: name, // toolTip: tooltip, // sortable: true, // width: width === -1 ? 50 : width, // getter: f, // } // } // } // }