Skip to content

Commit

Permalink
feat: add drag and drop column rearrangement for table viz (#19381)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevetracvc committed May 25, 2022
1 parent ce547f4 commit 7e9b85f
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function loadData(
alignPn = false,
showCellBars = true,
includeSearch = true,
allowRearrangeColumns = false,
},
): TableChartProps {
if (!props.queriesData || !props.queriesData[0]) return props;
Expand All @@ -86,6 +87,7 @@ function loadData(
page_length: pageLength,
show_cell_bars: showCellBars,
include_search: includeSearch,
allow_rearrange_columns: allowRearrangeColumns,
},
height: window.innerHeight - 130,
};
Expand Down Expand Up @@ -117,15 +119,20 @@ export const BigTable = ({ width, height }) => {
const cols = number('Columns', 8, { range: true, min: 1, max: 20 });
const pageLength = number('Page size', 50, { range: true, min: 0, max: 100 });
const includeSearch = boolean('Include search', true);
const alignPn = boolean('Algin PosNeg', false);
const alignPn = boolean('Align PosNeg', false);
const showCellBars = boolean('Show Cell Bars', true);
const allowRearrangeColumns = boolean(
'Allow end user to drag-and-drop column headers to rearrange them.',
false,
);
const chartProps = loadData(birthNames, {
pageLength,
rows,
cols,
alignPn,
showCellBars,
includeSearch,
allowRearrangeColumns,
});
return (
<SuperChart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
usePagination,
useSortBy,
useGlobalFilter,
useColumnOrder,
PluginHook,
TableOptions,
FilterType,
Expand Down Expand Up @@ -64,6 +65,7 @@ export interface DataTableProps<D extends object> extends TableOptions<D> {
sticky?: boolean;
rowCount: number;
wrapperRef?: MutableRefObject<HTMLDivElement>;
onColumnOrderChange: () => void;
}

export interface RenderHTMLCellProps extends HTMLProps<HTMLTableCellElement> {
Expand Down Expand Up @@ -95,12 +97,14 @@ export default typedMemo(function DataTable<D extends object>({
hooks,
serverPagination,
wrapperRef: userWrapperRef,
onColumnOrderChange,
...moreUseTableOptions
}: DataTableProps<D>): JSX.Element {
const tableHooks: PluginHook<D>[] = [
useGlobalFilter,
useSortBy,
usePagination,
useColumnOrder,
doSticky ? useSticky : [],
hooks || [],
].flat();
Expand Down Expand Up @@ -172,6 +176,8 @@ export default typedMemo(function DataTable<D extends object>({
setGlobalFilter,
setPageSize: setPageSize_,
wrapStickyTable,
setColumnOrder,
allColumns,
state: { pageIndex, pageSize, globalFilter: filterValue, sticky = {} },
} = useTable<D>(
{
Expand Down Expand Up @@ -211,6 +217,33 @@ export default typedMemo(function DataTable<D extends object>({

const shouldRenderFooter = columns.some(x => !!x.Footer);

let columnBeingDragged = -1;

const onDragStart = (e: React.DragEvent) => {
const el = e.target as HTMLTableCellElement;
columnBeingDragged = allColumns.findIndex(
col => col.id === el.dataset.columnName,
);
e.dataTransfer.setData('text/plain', `${columnBeingDragged}`);
};

const onDrop = (e: React.DragEvent) => {
const el = e.target as HTMLTableCellElement;
const newPosition = allColumns.findIndex(
col => col.id === el.dataset.columnName,
);

if (newPosition !== -1) {
const currentCols = allColumns.map(c => c.id);
const colToBeMoved = currentCols.splice(columnBeingDragged, 1);
currentCols.splice(newPosition, 0, colToBeMoved[0]);
setColumnOrder(currentCols);
// toggle value in TableChart to trigger column width recalc
onColumnOrderChange();
}
e.preventDefault();
};

const renderTable = () => (
<table {...getTableProps({ className: tableClassName })}>
<thead>
Expand All @@ -223,6 +256,8 @@ export default typedMemo(function DataTable<D extends object>({
column.render('Header', {
key: column.id,
...column.getSortByToggleProps(),
onDragStart,
onDrop,
}),
)}
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ function useInstance<D extends object>(instance: TableInstance<D>) {
data,
page,
rows,
allColumns,
getTableSize = () => undefined,
} = instance;

Expand All @@ -370,7 +371,7 @@ function useInstance<D extends object>(instance: TableInstance<D>) {
useMountedMemo(getTableSize, [getTableSize]) || sticky;
// only change of data should trigger re-render
// eslint-disable-next-line react-hooks/exhaustive-deps
const table = useMemo(renderer, [page, rows]);
const table = useMemo(renderer, [page, rows, allColumns]);

useLayoutEffect(() => {
if (!width || !height) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
UseSortByState,
UseTableHooks,
UseSortByHooks,
UseColumnOrderState,
UseColumnOrderInstanceProps,
Renderer,
HeaderProps,
TableFooterProps,
Expand Down Expand Up @@ -64,6 +66,7 @@ declare module 'react-table' {
UseRowSelectInstanceProps<D>,
UseRowStateInstanceProps<D>,
UseSortByInstanceProps<D>,
UseColumnOrderInstanceProps<D>,
UseStickyInstanceProps {}

export interface TableState<D extends object>
Expand All @@ -73,6 +76,7 @@ declare module 'react-table' {
UsePaginationState<D>,
UseRowSelectState<D>,
UseSortByState<D>,
UseColumnOrderState<D>,
UseStickyState {}

// Typing from @types/react-table is incomplete
Expand All @@ -82,12 +86,19 @@ declare module 'react-table' {
onClick?: React.MouseEventHandler;
}

interface TableRearrangeColumnsProps {
onDragStart: (e: React.DragEvent) => void;
onDrop: (e: React.DragEvent) => void;
}

export interface ColumnInterface<D extends object>
extends UseGlobalFiltersColumnOptions<D>,
UseSortByColumnOptions<D> {
// must define as a new property because it's not possible to override
// the existing `Header` renderer option
Header?: Renderer<TableSortByToggleProps & HeaderProps<D>>;
Header?: Renderer<
TableSortByToggleProps & HeaderProps<D> & TableRearrangeColumnsProps
>;
Footer?: Renderer<TableFooterProps<D>>;
}

Expand Down
21 changes: 18 additions & 3 deletions superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { CSSProperties, useCallback, useMemo } from 'react';
import React, { CSSProperties, useCallback, useMemo, useState } from 'react';
import {
ColumnInstance,
ColumnWithLooseAccessor,
Expand Down Expand Up @@ -192,12 +192,16 @@ export default function TableChart<D extends DataRecord = DataRecord>(
filters,
sticky = true, // whether to use sticky header
columnColorFormatters,
allowRearrangeColumns = false,
} = props;
const timestampFormatter = useCallback(
value => getTimeFormatterForGranularity(timeGrain)(value),
[timeGrain],
);

// keep track of whether column order changed, so that column widths can too
const [columnOrderToggle, setColumnOrderToggle] = useState(false);

const handleChange = useCallback(
(filters: { [x: string]: DataRecordValue[] }) => {
if (!emitFilter) {
Expand Down Expand Up @@ -413,7 +417,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
// render `Cell`. This saves some time for large tables.
return <StyledCell {...cellProps}>{text}</StyledCell>;
},
Header: ({ column: col, onClick, style }) => (
Header: ({ column: col, onClick, style, onDragStart, onDrop }) => (
<th
title="Shift + Click to sort by multiple columns"
className={[className, col.isSorted ? 'is-sorted' : ''].join(' ')}
Expand All @@ -422,6 +426,14 @@ export default function TableChart<D extends DataRecord = DataRecord>(
...style,
}}
onClick={onClick}
data-column-name={col.id}
{...(allowRearrangeColumns && {
draggable: 'true',
onDragStart,
onDragOver: e => e.preventDefault(),
onDragEnter: e => e.preventDefault(),
onDrop,
})}
>
{/* can't use `columnWidth &&` because it may also be zero */}
{config.columnWidth ? (
Expand All @@ -434,12 +446,13 @@ export default function TableChart<D extends DataRecord = DataRecord>(
/>
) : null}
<div
data-column-name={col.id}
css={{
display: 'inline-flex',
alignItems: 'center',
}}
>
<span>{label}</span>
<span data-column-name={col.id}>{label}</span>
<SortIcon column={col} />
</div>
</th>
Expand Down Expand Up @@ -469,6 +482,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
toggleFilter,
totals,
columnColorFormatters,
columnOrderToggle,
],
);

Expand Down Expand Up @@ -498,6 +512,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
height={height}
serverPagination={serverPagination}
onServerPaginationChange={handleServerPaginationChange}
onColumnOrderChange={() => setColumnOrderToggle(!columnOrderToggle)}
// 9 page items in > 340px works well even for 100+ pages
maxPageItemCount={width > 340 ? 9 : 7}
noResults={getNoResultsMessage}
Expand Down
14 changes: 14 additions & 0 deletions superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,20 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'allow_rearrange_columns',
config: {
type: 'CheckboxControl',
label: t('Allow columns to be rearranged'),
renderTrigger: true,
default: false,
description: t(
"Allow end user to drag-and-drop column headers to rearrange them. Note their changes won't persist for the next time they open the chart.",
),
},
},
],
[
{
name: 'column_config',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ const transformProps = (
query_mode: queryMode,
show_totals: showTotals,
conditional_formatting: conditionalFormatting,
allow_rearrange_columns: allowRearrangeColumns,
} = formData;
const timeGrain = extractTimegrain(formData);

Expand Down Expand Up @@ -272,6 +273,7 @@ const transformProps = (
onChangeFilter,
columnColorFormatters,
timeGrain,
allowRearrangeColumns,
};
};

Expand Down
2 changes: 2 additions & 0 deletions superset-frontend/plugins/plugin-chart-table/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type TableChartFormData = QueryFormData & {
emit_filter?: boolean;
time_grain_sqla?: TimeGranularity;
column_config?: Record<string, ColumnConfig>;
allow_rearrange_columns?: boolean;
};

export interface TableChartProps extends ChartProps {
Expand Down Expand Up @@ -109,6 +110,7 @@ export interface TableChartTransformedProps<D extends DataRecord = DataRecord> {
emitFilter?: boolean;
onChangeFilter?: ChartProps['hooks']['onAddFilter'];
columnColorFormatters?: ColorFormatters;
allowRearrangeColumns?: boolean;
}

export default {};

0 comments on commit 7e9b85f

Please sign in to comment.