williaster commented on a change in pull request #5186: Implement a React-based
table editor
URL:
https://github.com/apache/incubator-superset/pull/5186#discussion_r208047029
##########
File path: superset/assets/src/CRUD/CollectionTable.jsx
##########
@@ -0,0 +1,220 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import shortid from 'shortid';
+
+import Button from '../components/Button';
+import Fieldset from './Fieldset';
+import { recurseReactClone } from './utils';
+import './styles.css';
+
+const propTypes = {
+ collection: PropTypes.array,
+ itemGenerator: PropTypes.func,
+ columnLabels: PropTypes.object,
+ tableColumns: PropTypes.array,
+ columns: PropTypes.array,
+ onChange: PropTypes.func,
+ itemRenderers: PropTypes.object,
+ allowDeletes: PropTypes.bool,
+ expandFieldset: PropTypes.node,
+ emptyMessage: PropTypes.node,
+ extraButtons: PropTypes.node,
+ allowAddItem: PropTypes.bool,
+};
+const defaultProps = {
+ onChange: () => {},
+ itemRenderers: {},
+ columnLabels: {},
+ allowDeletes: false,
+ emptyMessage: 'No entries',
+ allowAddItem: false,
+ itemGenerator: () => ({}),
+};
+const Frame = props => (
+ <div className="frame">
+ {props.children}
+ </div>);
+Frame.propTypes = { children: PropTypes.node };
+
+function createKeyedCollection(arr) {
+ const newArr = arr.map(o => ({
+ ...o,
+ id: o.id || shortid.generate(),
+ }));
+ const map = {};
+ newArr.forEach((o) => {
+ map[o.id] = o;
+ });
+ return map;
+}
+
+export default class CRUDCollection extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ expandedColumns: {},
+ collection: createKeyedCollection(props.collection),
+ };
+ this.renderItem = this.renderItem.bind(this);
+ this.onAddItem = this.onAddItem.bind(this);
+ this.renderExpandableSection = this.renderExpandableSection.bind(this);
+ this.getLabel = this.getLabel.bind(this);
+ this.onFieldsetChange = this.onFieldsetChange.bind(this);
+ this.renderTableBody = this.renderTableBody.bind(this);
+ this.changeCollection = this.changeCollection.bind(this);
+ }
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.collection !== this.props.collection) {
+ this.setState({
+ collection: createKeyedCollection(nextProps.collection),
+ });
+ }
+ }
+ onCellChange(id, col, val) {
+ this.changeCollection({
+ ...this.state.collection,
+ [id]: {
+ ...this.state.collection[id],
+ [col]: val,
+ },
+ });
+
+ }
+ onAddItem() {
+ let newItem = this.props.itemGenerator();
+ if (!newItem.id) {
+ newItem = { ...newItem, id: shortid.generate() };
+ }
+ this.changeCollection({
+ ...this.state.collection,
+ [newItem.id]: newItem,
+ });
+ }
+ onFieldsetChange(item) {
+ this.changeCollection({
+ ...this.state.collection,
+ [item.id]: item,
+ });
+ }
+ getLabel(col) {
+ const { columnLabels } = this.props;
+ let label = columnLabels[col] ? columnLabels[col] : col;
+ if (label.startsWith('__')) {
+ label = '';
+ }
+ return label;
+ }
+ changeCollection(collection) {
+ this.setState({ collection });
+ this.props.onChange(Object.keys(collection).map(k => collection[k]));
+ }
+ deleteItem(id) {
+ const newColl = { ...this.state.collection };
+ delete newColl[id];
+ this.changeCollection(newColl);
+ }
+ effectiveTableColumns() {
+ const { tableColumns, allowDeletes, expandFieldset } = this.props;
+ const cols = allowDeletes ? tableColumns.concat(['__actions']) :
tableColumns;
+ return expandFieldset ? ['__expand'].concat(cols) : cols;
+ }
+ toggleExpand(id) {
+ this.onCellChange(id, '__expanded', false);
+ this.setState({
+ expandedColumns: {
+ ...this.state.expandedColumns,
+ [id]: !this.state.expandedColumns[id],
+ },
+ });
+ }
+ renderHeaderRow() {
+ const cols = this.effectiveTableColumns();
+ return (
+ <thead>
+ <tr>
+ {this.props.expandFieldset && <th className="tiny-cell" />}
+ {cols.map(col => <th key={col}>{this.getLabel(col)}</th>)}
+ {this.props.allowDeletes && <th className="tiny-cell" />}
+ </tr>
+ </thead>
+ );
+ }
+ renderExpandableSection(item) {
+ const propsGenerator = () => ({ item, onChange: this.onFieldsetChange });
+ return recurseReactClone(this.props.expandFieldset, Fieldset,
propsGenerator);
+ }
+ renderCell(record, col) {
+ const renderer = this.props.itemRenderers[col];
+ const val = record[col];
+ const onChange = this.onCellChange.bind(this, record.id, col);
+ return renderer ? renderer(val, onChange, this.getLabel(col)) : val;
+ }
+ renderItem(record) {
+ const { tableColumns, allowDeletes, expandFieldset } = this.props;
+ /* eslint-disable no-underscore-dangle */
+ const isExpanded = !!this.state.expandedColumns[record.id] ||
record.__expanded;
+ let tds = [];
+ if (expandFieldset) {
+ tds.push(
+ <td key="__expand" className="expand">
+ <i
+ className={`fa fa-caret-${isExpanded ? 'down' : 'right'}
text-primary pointer`}
+ onClick={this.toggleExpand.bind(this, record.id)}
+ />
+ </td>);
+ }
+ tds = tds.concat(tableColumns.map(col => (
+ <td key={col}>{this.renderCell(record, col)}</td>
+ )));
+ if (allowDeletes) {
+ tds.push(
+ <td key="__actions">
+ <i
+ className="fa fa-close text-primary pointer"
+ onClick={this.deleteItem.bind(this, record.id)}
+ />
+ </td>);
+ }
+ const trs = [<tr className="row" key={record.id}>{tds}</tr>];
+ if (isExpanded) {
Review comment:
the cleanest expandable tables I have seen have
1) separated the row component from the table itself (`Table` +
`ExpandableRow`) to decouple and to simplify the components
2) have implemented the expanded row with a trick so that you can
encapsulate the expansion in one component. the trick is to wrap every row
(previously `tr`) in a `tbody`, which can have 1 or more rows. so you can
render something like:
```jsx
// ExpandableRow.jsx
...
return (
<tbody>
<tr>{/* tds here */}</tr>
{this.props.isExpanded && <tr><td
colspan={x}>{this.props.expandedContent}</td></tr>
</tbody>
);
```
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]