Rendering sortable generic tables in the UI

Rendering generic table columns

Made Type a required attribute for TableTemplate

Made generic table sortable on the UI
This commit is contained in:
Filip Barl
2016-12-23 17:00:24 +01:00
parent 31be525bd2
commit e475a09ee6
6 changed files with 224 additions and 14 deletions

View File

@@ -1,3 +1,4 @@
import debug from 'debug';
import React from 'react';
import { connect } from 'react-redux';
import { Map as makeMap } from 'immutable';
@@ -8,6 +9,7 @@ import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils';
import MatchedText from './matched-text';
import NodeDetailsControls from './node-details/node-details-controls';
import NodeDetailsGenericTable from './node-details/node-details-generic-table';
import NodeDetailsHealth from './node-details/node-details-health';
import NodeDetailsInfo from './node-details/node-details-info';
import NodeDetailsLabels from './node-details/node-details-labels';
@@ -15,13 +17,18 @@ import NodeDetailsRelatives from './node-details/node-details-relatives';
import NodeDetailsTable from './node-details/node-details-table';
import Warning from './warning';
const logError = debug('scope:error');
function getTruncationText(count) {
return 'This section was too long to be handled efficiently and has been truncated'
+ ` (${count} extra entries not included). We are working to remove this limitation.`;
}
class NodeDetails extends React.Component {
const TABLE_TYPE_PROPERTY_LIST = 'property-list';
const TABLE_TYPE_GENERIC = 'multicolumn-table';
class NodeDetails extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClickClose = this.handleClickClose.bind(this);
@@ -214,9 +221,7 @@ class NodeDetails extends React.Component {
<Warning text={getTruncationText(table.truncationCount)} />
</span>}
</div>
<NodeDetailsLabels
rows={table.rows} controls={table.controls}
matches={nodeMatches.get('tables')} />
{this.renderTable(table)}
</div>
);
}
@@ -227,6 +232,29 @@ class NodeDetails extends React.Component {
);
}
renderTable(table) {
const { nodeMatches = makeMap() } = this.props;
switch (table.type) {
case TABLE_TYPE_GENERIC:
return (
<NodeDetailsGenericTable
rows={table.rows} columns={table.columns}
matches={nodeMatches.get('tables')}
/>
);
case TABLE_TYPE_PROPERTY_LIST:
return (
<NodeDetailsLabels
rows={table.rows} controls={table.controls}
matches={nodeMatches.get('tables')}
/>
);
default:
logError(`Undefined type '${table.type}' for table ${table.id}`);
return null;
}
}
componentDidUpdate() {
this.updateTitle();
}

View File

@@ -0,0 +1,129 @@
import React from 'react';
import { Map as makeMap } from 'immutable';
import { sortBy } from 'lodash';
import MatchedText from '../matched-text';
import ShowMore from '../show-more';
function columnStyle(column) {
return {
textAlign: column.dataType === 'number' ? 'right' : 'left',
paddingRight: '10px',
maxWidth: '140px'
};
}
function sortedRows(rows, sortedByColumn, sortedDesc) {
const orderedRows = sortBy(rows, row => row.id);
const sorted = sortBy(orderedRows, (row) => {
let value = row.entries[sortedByColumn.id];
if (sortedByColumn.dataType === 'number') {
value = parseFloat(value);
}
return value;
});
if (!sortedDesc) {
sorted.reverse();
}
return sorted;
}
export default class NodeDetailsGenericTable extends React.Component {
constructor(props, context) {
super(props, context);
this.DEFAULT_LIMIT = 5;
this.state = {
limit: this.DEFAULT_LIMIT,
sortedByColumn: props.columns[0],
sortedDesc: true
};
this.handleLimitClick = this.handleLimitClick.bind(this);
}
handleHeaderClick(ev, column) {
ev.preventDefault();
this.setState({
sortedByColumn: column,
sortedDesc: this.state.sortedByColumn.id === column.id
? !this.state.sortedDesc : true
});
}
handleLimitClick() {
const limit = this.state.limit ? 0 : this.DEFAULT_LIMIT;
this.setState({limit});
}
render() {
const { sortedByColumn, sortedDesc } = this.state;
const { columns, matches = makeMap() } = this.props;
let rows = this.props.rows;
let notShown = 0;
const limited = rows && this.state.limit > 0 && rows.length > this.state.limit;
const expanded = this.state.limit === 0;
if (rows && limited) {
const hasNotShownMatch = rows.filter((row, index) => index >= this.state.limit
&& matches.has(row.id)).length > 0;
if (!hasNotShownMatch) {
notShown = rows.length - this.DEFAULT_LIMIT;
rows = rows.slice(0, this.state.limit);
}
}
return (
<div className="node-details-generic-table">
<table>
<thead>
<tr>
{columns.map((column) => {
const onHeaderClick = (ev) => {
this.handleHeaderClick(ev, column);
};
const isSorted = column.id === this.state.sortedByColumn.id;
const isSortedDesc = isSorted && this.state.sortedDesc;
const isSortedAsc = isSorted && !isSortedDesc;
const style = Object.assign(columnStyle(column), {
cursor: 'pointer',
fontSize: '11px'
});
return (
<th
className="node-details-generic-table-header"
key={column.id} style={style} onClick={onHeaderClick}>
{isSortedAsc
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
{isSortedDesc
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
{column.label}
</th>
);
})}
</tr>
</thead>
<tbody>
{sortedRows(rows, sortedByColumn, sortedDesc).map(row => (
<tr className="node-details-generic-table-row" key={row.id}>
{columns.map((column) => {
const value = row.entries[column.id];
const match = matches.get(column.id);
return (
<td
className="node-details-generic-table-field-value truncate"
title={value} key={column.id} style={columnStyle(column)}>
<MatchedText text={value} match={match} />
</td>
);
})}
</tr>
))}
</tbody>
</table>
<ShowMore
handleClick={this.handleLimitClick} collection={this.props.rows}
expanded={expanded} notShown={notShown}
/>
</div>
);
}
}