From 31d4c8613e98d24a1198baba9041cae3f5cd2a71 Mon Sep 17 00:00:00 2001 From: Soichi Takamura Date: Thu, 4 Jan 2024 16:05:07 +0900 Subject: [PATCH] respectColumnLevels --- README.md | 9 ++++++- src/merge-cells.test.ts | 26 ++++++++++++++++--- src/merge-cells.ts | 55 ++++++++++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e7a709f..d2df51d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ calcRowspanFromObjectArray( Apply `rowspan` to an HTML table, calculated by `calcRowspanFromObjectArray`. Signature: ```ts -function mergeCells(tableEl: HTMLTableElement, columnIndexes?: number[]): void +function mergeCells(tableEl: HTMLTableElement, columnIndexes?: number[], options?: Options): void ``` ### `calcRowspanFromObjectArray` @@ -60,9 +60,16 @@ rendering. Signature: function calcRowspanFromObjectArray>( rows: E[], columnKeys: string[], + options?: Options, ): Record ``` +### `Options` + +| property | default value | description | +| --------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `respectColumnLevels` | `true` | Whether to merge/separate cells based on column levels. Leave it to `true` if you can aggregate your table with `GROUP BY`. | + ## Publishing ```shell diff --git a/src/merge-cells.test.ts b/src/merge-cells.test.ts index 8af68b1..5bb2bbc 100644 --- a/src/merge-cells.test.ts +++ b/src/merge-cells.test.ts @@ -86,9 +86,29 @@ describe('lib.test.ts', () => { ['c_1', 'c_2'], ), ).toStrictEqual([ - { c_2: 1, c_1: 1 }, - { c_2: 1, c_1: 1 }, - { c_2: 1, c_1: 1 }, + { c_1: 1, c_2: 1 }, + { c_1: 1, c_2: 1 }, + { c_1: 1, c_2: 1 }, + ]) + }) + + test('merge vertically regardless of levels', () => { + expect( + calcRowspanFromObjectArray( + [ + { c_1: 'a', c_2: 'a', c_3: 'c' }, + { c_1: 'a', c_2: 'b', c_3: 'c' }, + { c_1: 'b', c_2: 'b', c_3: 'a' }, + ], + ['c_1', 'c_2', 'c_3'], + { + respectColumnLevels: false, + }, + ), + ).toStrictEqual([ + { c_1: 2, c_2: 1, c_3: 2 }, + { c_1: 0, c_2: 2, c_3: 0 }, + { c_1: 1, c_2: 0, c_3: 1 }, ]) }) }) diff --git a/src/merge-cells.ts b/src/merge-cells.ts index 8ce3db1..bba26d5 100644 --- a/src/merge-cells.ts +++ b/src/merge-cells.ts @@ -7,12 +7,21 @@ interface BaseRow { rowspan: Rowspan } +interface Options { + respectColumnLevels?: boolean +} + type ValueProvider = (row: E, columnKey: K) => any +const defaultOptions: Options = { + respectColumnLevels: true, +} + export function calcRowspanRecur( rows: E[], fields: K[], // Object keys or array indexes valueProvider: ValueProvider, + options: Options, baseRows: (BaseRow | undefined)[] = [], acc: Rowspan[] = [], ): Rowspan[] { @@ -30,28 +39,37 @@ export function calcRowspanRecur( } as BaseRow acc.push(rowspan) - const nextBaseRows = [] as BaseRow[] + const nextBaseRows = options.respectColumnLevels ? ([] as BaseRow[]) : baseRows + for (let i = 0; i < fields.length; i++) { const field = fields[i] const baseRow = baseRows[i] const isNewValue = !baseRow || valueProvider(top, field) !== valueProvider(baseRow.row, field) + if (isNewValue) { - return calcRowspanRecur( - rest, - fields, - valueProvider, - [...nextBaseRows, ...Array(fields.length - nextBaseRows.length).fill(topAcc)], - acc, - ) + if (options.respectColumnLevels) { + return calcRowspanRecur( + rest, + fields, + valueProvider, + options, + [...nextBaseRows, ...Array(fields.length - nextBaseRows.length).fill(topAcc)], + acc, + ) + } else { + baseRows[i] = topAcc + } + } else { + baseRows[i]!!.rowspan[field]++ + topAcc.rowspan[field] = 0 + if (options.respectColumnLevels) { + nextBaseRows.push(baseRow) + } } - - baseRow.rowspan[field]++ - topAcc.rowspan[field] = 0 - nextBaseRows.push(baseRow) } - return calcRowspanRecur(rest, fields, valueProvider, nextBaseRows, acc) + return calcRowspanRecur(rest, fields, valueProvider, options, nextBaseRows, acc) } export function applyRowspanToTable(tableEl: HTMLTableElement, columnIndexes?: number[]) { @@ -78,15 +96,22 @@ export function applyRowspanToTable(tableEl: HTMLTableElement, columnIndexes?: n export function calcRowspanWithTableRows( rows: HTMLCollectionOf, columnIndexes: number[] = rows[0] ? Array.from(Array(rows[0].cells.length).keys()) : [], + options: Options = defaultOptions, ) { - return calcRowspanRecur(Array.from(rows), columnIndexes, (row, fieldIndex) => row.cells[fieldIndex]!!.textContent) + return calcRowspanRecur( + Array.from(rows), + columnIndexes, + (row, fieldIndex) => row.cells[fieldIndex]!!.textContent, + options, + ) } export function calcRowspanFromObjectArray>( rows: E[], columnKeys: string[], + options: Options = defaultOptions, ): Rowspan[] { - return calcRowspanRecur(rows, columnKeys, (row, field) => row[field]!!) + return calcRowspanRecur(rows, columnKeys, (row, field) => row[field]!!, options) } export const mergeCells = applyRowspanToTable