Finished core functionality.

This commit is contained in:
2022-09-12 18:19:57 -07:00
parent bbff674b62
commit 6fe980ae6e
11 changed files with 608 additions and 194 deletions

View File

@@ -0,0 +1,199 @@
import { Meteor } from 'meteor/meteor';
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import _ from 'lodash';
//{columns.map((column) => <td>{column.value(row)}</td>)}
const WholeRow = ({row, columns}) => {
return <tr><td>Test</td></tr>
}
const ColumnHeader = ({column}) => {
// console.log("Rendering column header")
return <td>
{column.isActions ?
"TODO: Add widgets"
:
column.title
}
</td>
}
const Row = ({columns, row, getRowKey, editedRowKey, editor, setEdited}) => {
return <tr><td>Test</td></tr>
}
/*
<tr>
{!editedRowKey || getRowKey(row) !== editedRowKey ?
columns.map((column, i) => <Cell column={column} row={row}/>)
:
editor
}
</tr>
*/
// onDoubleClick={(e) => {(!editedRowKey || getRowKey(row) !== editedRowKey) && setEdited(row)}}
const Cell = ({row, column}) => {
return <td>test</td>
}
/*
<td>
{column.isActions ?
"TODO"
:
column.value(row)
}
</td>
*/
/**
* Example:
* import {useState} from 'react';
* const[edited, setEdited] = useState(undefined);
* <GridTable setEdited={setEdited} edited={edited} rows={rows} columns={columnns} actions={actions}>
* <!-- Editor JSX here. -->
* </GridTable>
*
* export let rows;
export let columns;
export let rowKey; //Must only be null/undefined if the row is a new object (not associated with a row in the table). Should not change.
export let edited;
export let actions;
export let selection;
* @param props
* @returns {JSX.Element}
*/
export default ({columns, rows, actions, getRowKey, edited, setEdited, children}) => {
// Setup a width for each column.
columns.forEach(column => {
let min = column.minWidth ? Math.max(10, column.minWidth) : 10;
let weight = column.weight ? Math.max(1, column.weight) : 1;
column.width = 'minmax(' + min + 'px, ' + weight + 'fr)';
column.isActions = false;
});
// Add the actions column to the end.
// TODO: Allow it to be positioned.
if(actions) {
// TODO: make this fixed width based on the possible widget widths.
actions.width = 'minmax(10px, 1fr)';
actions.isActions = true;
columns[columns.length] = actions;
}
// Collect the column widths for the layout.
let gridTemplateColumns = columns.map(({width}) => width).join(' ');
// Resize column code.
let headerBeingResized = null;
let horizontalScrollOffset = 0;
const initResize = ({target}) => {
headerBeingResized = target.parentNode;
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', completeResize);
headerBeingResized.classList.add('header--being-resized');
};
const completeResize = () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', completeResize);
headerBeingResized.classList.remove('header--being-resized');
headerBeingResized = null;
};
const onMouseMove = e => {
try {
// Calculate the desired width.
horizontalScrollOffset = document.documentElement.scrollLeft;
let parentX = Math.round(headerBeingResized.getBoundingClientRect().x);
const width = horizontalScrollOffset + (e.clientX - parentX);
// Update the column object with the new size value.
const column = columns.find(({element}) => element === headerBeingResized);
column.width = Math.max(column.minWidth, width) + "px";
// Ensure all the column widths are converted to fixed sizes.
columns.forEach((column, index) => {
if((index < columns.length - 1) && (column.width.startsWith('minmax'))) {
column.width = parseInt(column.element.clientWidth, 10) + 'px';
}
});
// Render the new column sizes.
gridTemplateColumns = columns.map(({width}) => width).join(' ');
} catch(e) {console.log(e);}
}
// Select row code.
let selectedRowElement = null;
const selectRow = (e, row) => {
let element = e.target;
while(element && element.nodeName !== "TR") element = element.parentNode;
if(selectedRowElement) {
selectedRowElement.classList.remove('selected');
}
selectedRowElement = element;
element.classList.add('selected');
dispatch('selection', selectedRowElement.dataset.key);
}
// Edit row code.
let editorContainer;
let editorTable;
// Listen for changes to the edited variable. Move the editor row and display it when there is a value.
// Relies on the table row being hidden when the edited value matches the row's value.
useTracker(() => {
if(editorContainer) {
if(edited) {
let id = rowKey($edited);
let hiddenRow = editorTable.querySelector('tbody tr[data-key="' + id + '"]');
if(!hiddenRow) {
let body = editorTable.querySelector('tbody');
body.firstChild ? body.insertBefore(editorContainer, body.firstChild) : body.appendChild(editorContainer);
editorContainer.classList.remove('hidden');
}
else {
//let editor = hiddenRow.querySelector('.editor');
let body = editorTable.querySelector('tbody');
let next = hiddenRow.nextSibling;
//editor.appendChild(editorContainer);
next ? body.insertBefore(editorContainer, next) : body.appendChild(editorContainer);
editorContainer.classList.remove('hidden');
}
}
else {
console.log("Edited cleared");
editorContainer.classList.add('hidden');
}
}
});
let editedRowKey = edited ? getRowKey(edited) : undefined;
console.log("Rendering grid table")
// let contents = "";
//
// for(let row of rows) {
// contents += <WholeRow row={row} columns={columns}/>
// // contents += <tr>
// // for(let column of columns) {
// // contents += <CellValue row={row} column={column}></CellValue>
// // }
// // contents += </tr>
// }
return <div className={'grid-table-container'}>
<table style={{gridTemplateColumns}}>
<thead>
<tr>
{columns.map((column, i) => <ColumnHeader key={column.key} column={column}/>)}
</tr>
</thead>
<tbody>
{/*{rows.map((row, i) => <Row key={getRowKey(row)} row={row} columns={columns} getRowKey={getRowKey} editedRowKey={editedRowKey} editor={children} setEdited={setEdited}/>)}*/}
{rows.map((row)=><WholeRow row={row} columns={columns}/>)}
</tbody>
</table>
</div>
}

View File

@@ -0,0 +1,56 @@
import { Meteor } from 'meteor/meteor';
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import _ from 'lodash';
export default class GridTable2 extends React.Component {
constructor(props) {
super(props)
this.state = {
rows: [{_id: 1234, name: "Fred"}, {_id: 1235, }],
columns: [{id: "first", value: row => {return row._id}}, {id: "second", value: row => {return row.name}}]
}
}
render() {
console.log("Rendering GridTable2")
return <table><tbody>
<tr><td>Test</td><td>Test2</td></tr>
{/*<tr>*/}
{/*<GridTableCell row={this.state.row} column={this.state.column}></GridTableCell>*/}
{/*</tr>*/}
{/*{this.state.rows.map((row) => <GridTableRow key={row._id} row={row} columns={this.state.columns}></GridTableRow>)}*/}
</tbody></table>
}
}
class GridTableRow extends React.Component {
constructor(props) {
super(props)
this.state = {
columns: props.columns,
row: props.row,
}
}
render() {
console.log("Rendering GridTableRow")
return <tr>
{this.state.columns.map((column)=><GridTableCell key={this.state.row._id + "-" + column.id} row={this.state.row} column={column}/>)}
</tr>
}
}
class GridTableCell extends React.Component {
constructor(props) {
super(props)
console.log(props);
this.state = {
row: props.row,
column: props.column,
value: props.column.value(props.row)
}
}
render() {
console.log("Rendering GridTableCell")
return <td>{this.state.value}</td>
}
}

View File

@@ -32,11 +32,15 @@ import Box from "@mui/material/Box";
// let options = {
// key: (row) => row._id,
// editor: (row) => {return (<MyRowEditor value={row}/>)}
// add: true,
// maxHeight: "40rem",
// remove: (row) => { /* show dialog and/or perform remove */ }
// }
const cssTopControls = {
display: 'flex',
flexDirection: 'row-reverse',
flexDirection: 'row',
justifyContent: 'flex-end',
}
/*
@@ -79,6 +83,10 @@ export default ({columns, rows, options}) => {
setEdited({})
e.stopPropagation()
}
if(!edited && e.key === 'Delete' && selected && options.delete && _.isFunction(options.delete)) {
options.delete(selected)
}
}
const sort = (e, column) => {
@@ -119,7 +127,10 @@ export default ({columns, rows, options}) => {
// console.log(rows)
return (
<div className='simpleTableContainer'>
{options.add && <div style={cssTopControls}><Button variant="text" className="button" onClick={addRow}>Add</Button></div>}
<div style={cssTopControls}>
{options.add && <Button variant="text" className="button" onClick={addRow}>Add</Button>}
{options.remove && _.isFunction(options.remove) && <Button disabled={!selected} variant='text' className='button' onClick={() => {selected && options.remove(selected)}}>Remove</Button>}
</div>
<TableContainer className="simpleTable" component={Paper} style={containerStyle}>
<Table size="small" aria-label="Table" tabIndex="0" onKeyDown={keyHandler}>
<TableHead className="sticky">