Reputation: 390
I have a nested tree of table columns. Here are the typescript types.
export interface TableLeafColumn {
label: string;
key?: string;
}
export interface TableContainerColumn {
label: string;
columns: TableColumn[];
}
// Union type to represent either a leaf or container column
export type TableColumn = TableLeafColumn<T> | TableContainerColumn;
Here's an example of such a type:
const columns: TableColumn[] = [
{
label: "Name",
key: "name",
},
{
label: "Details",
columns: [
{
label: "Age",
key: "age",
},
{
label: "Address",
columns: [
{ label: "City", key: "city" },
{ label: "Country", key: "country" },
],
},
],
},
];
I would like a function called flattenColumns
that will return TableColumn[][]
. For example, the input above would return:
const result = [
[
{ label: "" }, // placeholder for Name
{ label: "Details", colSpan: 3 }
],
[
{ label: "" }, // placeholder for Name
{ label: "" }, // placeholder for Age
{ label: "Address", colSpan: 2 }
],
[
{ label: "Name", key: "name" },
{ label: "Age", key: "age" },
{ label: "City", key: "city" },
{ label: "Country", key: "country" },
]
]
I have tried to write the function, but nothing worked. The tricky party is adding the placeholders, so that every leaf column appears at the bottom.
Not sure if the result I gave above is the best representation for this, but I want to render such table headers inside an HTML table, and I figured that format would be the easiest to utilise.
Here is my (non-working) attempt.
export function isContainerColumn(
col: TableColumn,
): col is TableContainerColumn {
return "columns" in col && Array.isArray(col.columns);
}
interface FlatColumn extends TableLeafColumn {
colSpan: number;
}
export function flattenColumns(
columns: TableColumn[],
): FlatColumn[][] {
const levels: FlatColumn[][] = [];
function flatten(
cols: TableColumn<any>[],
depth: number,
): number {
levels[depth] ??= [];
let totalSpan = 0;
cols.forEach((col) => {
if (isContainerColumn(col)) {
const childSpan = flatten(col.columns, depth + 1);
totalSpan += childSpan;
levels[depth]!.push({
...col,
colSpan: childSpan,
});
}
else {
levels[depth]!.push({
...col,
colSpan: 1,
});
totalSpan += 1;
}
});
return totalSpan;
}
flatten(columns, 0);
return levels;
}
Upvotes: 2
Views: 68
Reputation: 386766
You could get colSpan
for nested items first and then get rows for each level with prefilling of values.
A more complex example b
with
[
{
label: "abcdefg",
columns: [
{ label: "a", key: "a" },
{
label: "bcdefg",
columns: [
{
label: "bcd",
columns: [
{
label: "bc",
columns: [
{ label: "b", key: "b" },
{ label: "c", key: "c" }
]
},
{ label: "d", key: "d" }
]
},
{ label: "e", key: "e" },
{
label: "fg",
columns: [
{ label: "f", key: "f" },
{ label: "g", key: "g" }
]
}
]
}
]
},
{
label: "hij",
columns: [
{ label: "h", key: "h" },
{
label: "ij",
columns: [
{ label: "i", key: "i"},
{ label: "j", key: "j"}
]
}
]
}
]
result:
| abcdefg | hij |
| | bcdefg | | ij |
| | bcd | | fg | | | |
| | bc | | | | | | | |
| a | b | c | d | e | f | g | h | i | j |
1 2 3 4 5 6 7 8 9 10
const
convert = columns => {
const
sizes = new WeakMap,
getSizes = (o, wm) => {
const size = o.columns.reduce((cs, q) => cs + (!q.columns || getSizes(q, wm)), 0) || 1
wm.set(o, size);
return size;
},
temp = { columns },
values = [],
result = [],
indices = [],
iterate = (o, level = 0) => {
result[level] ??= [];
indices[level] ??= 0;
while (indices[level] < values.length) {
result[level].push({ label: '' });
indices[level]++;
}
o.columns.forEach(q => {
if (q.columns) {
const colSpan = sizes.get(q);
result[level].push({ label: q.label, colSpan });
indices[level] += colSpan;
iterate(q, level + 1);
} else {
values.push(q);
result[level].push({ label: '' });
indices[level]++;
}
});
};
getSizes(temp, sizes);
iterate(temp);
result.length--;
for (let i = 0; i < result.length; i++) {
while (indices[i] < sizes.get(temp)) {
result[i].push({ label: '' });
indices[i]++;
}
}
result.push(values);
return result;
},
a = [{ label: "Name", key: "name" }, { label: "Details", columns: [{ label: "Age", key: "age" }, { label: "Address", columns: [{ label: "City", key: "city" }, { label: "Country", key: "country" }] }] }],
b = [{ label: "abcdefg", columns: [{ label: "a", key: "a" }, { label: "bcdefg", columns: [{ label: "bcd", columns: [{ label: "bc", columns: [{ label: "b", key: "b" }, { label: "c", key: "c" }] }, { label: "d", key: "d" }] }, { label: "e", key: "e" }, { label: "fg", columns: [{ label: "f", key: "f" }, { label: "g", key: "g" }] }] }] }, { label: "hij", columns: [{ label: "h", key: "h" }, { label: "ij", columns: [{ label: "i", key: "i" }, { label: "j", key: "j" }] }] }];
console.log(convert(a));
console.log(convert(b));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 2