diff --git a/packages/core/README.md b/packages/core/README.md index 0d4188d..3aab974 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1 +1 @@ -# Data Den Core +# Data Den Core \ No newline at end of file diff --git a/packages/core/index.ts b/packages/core/index.ts index 438d45d..65fab37 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -205,7 +205,7 @@ const options2: DataDenOptions = { caseSensitive: false, }, resize: true, - width: 260, + width: 150, cellRenderer: DataDenDefaultCellRenderer, }, { @@ -220,7 +220,7 @@ const options2: DataDenOptions = { caseSensitive: false, }, resize: true, - width: 200, + width: 150, cellRenderer: DataDenDefaultCellRenderer, editable: true, }, @@ -241,7 +241,7 @@ const options2: DataDenOptions = { }, }, resize: true, - width: 210, + width: 150, cellRenderer: DataDenDefaultCellRenderer, editable: true, }, @@ -253,7 +253,7 @@ const options2: DataDenOptions = { debounceTime: 500, }, resize: false, - width: 180, + width: 150, cellRenderer: DataDenDefaultCellRenderer, editable: true, }, @@ -272,7 +272,7 @@ const options2: DataDenOptions = { caseSensitive: false, }, resize: true, - width: 220, + width: 150, cellRenderer: DataDenDefaultCellRenderer, editable: true, }, @@ -291,7 +291,7 @@ const options2: DataDenOptions = { caseSensitive: false, }, resize: true, - width: 180, + width: 150, cellRenderer: DataDenDefaultCellRenderer, editable: true, }, diff --git a/packages/core/src/data-den-options.interface.ts b/packages/core/src/data-den-options.interface.ts index 669483d..935c6f1 100644 --- a/packages/core/src/data-den-options.interface.ts +++ b/packages/core/src/data-den-options.interface.ts @@ -42,6 +42,7 @@ export interface DataDenColDef { editable?: boolean | ((...args: any[]) => boolean); defaultSort?: 'asc' | 'desc' | null; sortOrder?: ('asc' | 'desc' | null)[]; + grouped?: boolean; } export interface DataDenDefaultColDef { diff --git a/packages/core/src/data-den-pub-sub.ts b/packages/core/src/data-den-pub-sub.ts index d3253d8..2a868bd 100644 --- a/packages/core/src/data-den-pub-sub.ts +++ b/packages/core/src/data-den-pub-sub.ts @@ -27,6 +27,9 @@ export class DataDenPubSub { 'info:fetch:done': [], 'command:pin-column:start': [], 'command:rerendering:done': [], + 'command:group-column:start': [], + 'command:ungroup-column:start': [], + 'command:group:update': [] }; publish(name: string, data: DataDenPublishedEvent) { diff --git a/packages/core/src/data-den.ts b/packages/core/src/data-den.ts index dd4a797..83b8161 100644 --- a/packages/core/src/data-den.ts +++ b/packages/core/src/data-den.ts @@ -40,7 +40,6 @@ export class DataDen { if (this.#dataLoaderStrategy) { this.#fetch = new DataDenFetchService(this.#dataLoaderStrategy, this.PubSub); } - this.#rendering = new DataDenRenderingService(container, gridOptions, this.PubSub); this.#sorting = new DataDenSortingService(gridOptions, this.PubSub); this.#filtering = new DataDenFilteringService(gridOptions, this.PubSub); diff --git a/packages/core/src/modules/dragging/data-den-dragging-service.ts b/packages/core/src/modules/dragging/data-den-dragging-service.ts index ee1e85c..53c47e3 100644 --- a/packages/core/src/modules/dragging/data-den-dragging-service.ts +++ b/packages/core/src/modules/dragging/data-den-dragging-service.ts @@ -1,7 +1,7 @@ -import { DataDenInternalOptions } from '../../data-den-options.interface'; +import { DataDenColDef, DataDenInternalOptions } from '../../data-den-options.interface'; import { DataDenPubSub } from '../../data-den-pub-sub'; -import { Context } from '../../context'; import { getMainColumnsOrder } from '../../utils/columns-order'; +import { DataDenEvent } from '../../data-den-event'; export class DataDenDraggingService { #container: HTMLElement; @@ -20,6 +20,9 @@ export class DataDenDraggingService { #mainColumnsOrder: number[]; #defaultGridOffsetLeft: number; #cssPrefix: string; + #groupedColumns: string[] = []; + #mainOrderedColumns: DataDenColDef[]; + #columnsOrder: number[] = []; #handleGridMouseMove: (e: MouseEvent) => void; #handleDocumentMouseUp: (e: MouseEvent) => void; @@ -40,15 +43,18 @@ export class DataDenDraggingService { this.#columnPositions = []; this.#breakpoints = []; this.#mainColumnsOrder = []; + this.#groupedColumns = []; this.#defaultGridOffsetLeft = 0; this.#cssPrefix = options.cssPrefix; + this.#mainOrderedColumns = options.columns; - this.#handleGridMouseMove = () => {}; - this.#handleDocumentMouseUp = () => {}; - this.#handleHeaderMouseDown = () => {}; + this.#handleGridMouseMove = () => { }; + this.#handleDocumentMouseUp = () => { }; + this.#handleHeaderMouseDown = () => { }; this.#subscribeFetchDone(); this.#subscribeRerenderingDone(); + this.#subscribeGroupUpdate(); } init() { @@ -69,7 +75,9 @@ export class DataDenDraggingService { update() { const tempColumns = [...this.#getAllColumnElements()]; - this.#columns = this.#mainColumnsOrder.map((columnIndex) => tempColumns[columnIndex]); + this.#columns = this.#mainColumnsOrder.map((columnIndex) => { + return tempColumns[columnIndex] + }); } #subscribeFetchDone() { @@ -102,7 +110,7 @@ export class DataDenDraggingService { } #setColumnParams() { - this.#columnPositions = [...this.#getAllColumnPositions()]; + this.#columnPositions = this.#columnPositions.length === 0 ? [...this.#getAllColumnPositions()] : this.#columnPositions; this.#setBreakpoints(); this.#defaultGridOffsetLeft = this.#gridMain!.getBoundingClientRect().left + parseFloat(this.#headerMainCellsWrapper!.style.left); @@ -139,7 +147,7 @@ export class DataDenDraggingService { const colHeader = this.#headers[index]; const rows = this.#container.querySelectorAll('[ref="row"]'); const cells = Array.from(rows).map( - (row) => row.querySelectorAll('.data-den-main-cells-wrapper [ref="cell"]')[index] as HTMLElement + (row) => row.querySelectorAll('.data-den-main-cells-wrapper [ref="cell"]')[this.#mainColumnsOrder[index]] as HTMLElement ); return [colHeader, ...cells]; @@ -156,7 +164,7 @@ export class DataDenDraggingService { } #setDefaultColumnsOrder() { - this.#mainColumnsOrder = getMainColumnsOrder(this.#options.columns); + this.#mainColumnsOrder = this.#mainColumnsOrder.length ? this.#mainColumnsOrder : getMainColumnsOrder(this.#mainOrderedColumns); } #subscribeResizingDone() { @@ -205,7 +213,7 @@ export class DataDenDraggingService { this.#targetIndex = this.#getMinBreakpointIndex(this.#breakpoints, offsetX); const currentColumnWidth = this.#columnPositions[this.#currentIndex].width; - const gap = this.#getColumnsGap(this.#currentIndex); + const gap = this.#getColumnsGap(this.#currentIndex, this.#targetIndex); // prevent swapping if there is no space for it (current column is wider than target column) if ( @@ -271,6 +279,41 @@ export class DataDenDraggingService { return array.length - 1; } + #subscribeGroupUpdate() { + this.PubSub.subscribe('command:group:update', (event: DataDenEvent) => { + this.#currentIndex = this.#getMinBreakpointIndex(this.#breakpoints, event.data.pageX); + const len = event.data.groupedColumns.length; + + // handle main columns order + const columnPositionsBefore = this.#mainColumnsOrder.splice(len - 1, this.#currentIndex - len + 1); + this.#mainColumnsOrder.splice(len, 0, ...columnPositionsBefore) + // handle main columns order + + // handle column positions + const colsBefore = this.#columnPositions.splice(len - 1, this.#currentIndex - len + 1); + this.#columnPositions.splice(len, 0, ...colsBefore); + // handle column positions + + + // @TODO update columns + + setTimeout(() => { + console.log(this.#columns); + + const columnsBefore = this.#columns.splice(len - 1, this.#currentIndex - len + 1); + this.#columns.splice(len, 0, ...columnsBefore); + console.log(this.#columns); + }, 0); + + // setTimeout(() => { + // console.log(this.#columns); + // }, 0); + + // @TODO update breakpoints positions + + }); + } + #updateColumnPositions() { if (this.#currentIndex === this.#targetIndex && this.#prevTargetIndex === -1) { return; @@ -278,7 +321,8 @@ export class DataDenDraggingService { const direction = this.#getDirection(); const currentOrderedIndex = direction === 'right' ? this.#targetIndex - 1 : this.#targetIndex + 1; - const gap = this.#getColumnsGap(currentOrderedIndex); + + const gap = this.#getColumnsGap(currentOrderedIndex, this.#targetIndex); if (direction === 'right') { this.#breakpoints[this.#targetIndex] = this.#breakpoints[this.#targetIndex] + gap; @@ -295,18 +339,6 @@ export class DataDenDraggingService { cell.style.left = `${this.#breakpoints[index]}px`; }); }); - - this.#publishColumnsOrder(); - } - - #getDirection() { - return this.#prevTargetIndex > this.#targetIndex ? 'left' : 'right'; - } - - #getColumnsGap(sourceIndex: number) { - const sourceColumnWidth = this.#columnPositions[sourceIndex].width; - const targetColumnWidth = this.#columnPositions[this.#targetIndex].width; - return targetColumnWidth - sourceColumnWidth; } #swapArrayElements(array: HTMLElement[][] | any[], sourceIndex: number, targetIndex: number) { @@ -319,6 +351,16 @@ export class DataDenDraggingService { array[targetIndex] = temp; } + #getDirection() { + return this.#prevTargetIndex > this.#targetIndex ? 'left' : 'right'; + } + + #getColumnsGap(sourceIndex: number, targetIndex: number) { + const sourceColumnWidth = this.#columnPositions[sourceIndex].width; + const targetColumnWidth = this.#columnPositions[targetIndex].width; + return targetColumnWidth - sourceColumnWidth; + } + #finalizeDragging() { this.#isDragging = false; this.#disableTransition(); @@ -343,11 +385,4 @@ export class DataDenDraggingService { this.#container.removeEventListener('mousemove', this.#handleGridMouseMove); document.removeEventListener('mouseup', this.#handleDocumentMouseUp); } - - #publishColumnsOrder() { - this.PubSub.publish('info:dragging:columns-reorder:done', { - columnsOrder: this.#mainColumnsOrder, - context: new Context('info:dragging:columns-reorder:done'), - }); - } } diff --git a/packages/core/src/modules/fetch/data-den-fetch-options.interface.ts b/packages/core/src/modules/fetch/data-den-fetch-options.interface.ts index 359bebd..811cfd5 100644 --- a/packages/core/src/modules/fetch/data-den-fetch-options.interface.ts +++ b/packages/core/src/modules/fetch/data-den-fetch-options.interface.ts @@ -25,9 +25,14 @@ export interface DataDenPaginationOptions { lastRowIndex: number; } +export interface DataDenGroupedOptions { + groupedColumns?: any[]; +} + export interface DataDenFetchOptions { sortingOptions?: DataDenSortOptions; quickFilterOptions?: DataDenQuickFilterOptions; filtersOptions?: DataDenFiltersOptions; paginationOptions?: DataDenPaginationOptions; + groupedOptions?: DataDenGroupedOptions; } diff --git a/packages/core/src/modules/fetch/data-den-fetch-service.ts b/packages/core/src/modules/fetch/data-den-fetch-service.ts index 95a1fdc..d77b28f 100644 --- a/packages/core/src/modules/fetch/data-den-fetch-service.ts +++ b/packages/core/src/modules/fetch/data-den-fetch-service.ts @@ -18,6 +18,7 @@ export class DataDenFetchService { this.#subscribeQuickFilterChanged(); this.#subscribeFilterChange(); this.#subscribePaginationChange(); + this.#subscribeGroupChange(); } #getData(fetchOptions: DataDenFetchOptions): Promise { @@ -92,4 +93,16 @@ export class DataDenFetchService { }); }); } + + #subscribeGroupChange(): void { + this.PubSub.subscribe('command:group:update', (event: DataDenEvent) => { + this.#fetchOptions.groupedOptions = event.data.groupedColumns.length === 0 ? undefined : { + groupedColumns: event.data.groupedColumns, + }; + + this.#getData({ groupedOptions: this.#fetchOptions.groupedOptions }).then((data: any[]) => { + this.#publishFetchDone(event.context, data); + }); + }); + } } diff --git a/packages/core/src/modules/fetch/strategy/data-den-client-data-loader-strategy-client.ts b/packages/core/src/modules/fetch/strategy/data-den-client-data-loader-strategy-client.ts index 9a5686f..773eade 100644 --- a/packages/core/src/modules/fetch/strategy/data-den-client-data-loader-strategy-client.ts +++ b/packages/core/src/modules/fetch/strategy/data-den-client-data-loader-strategy-client.ts @@ -2,11 +2,12 @@ import { DataDenDataLoaderStrategy } from './data-den-data-loader-strategy'; import { DataDenFetchOptions, DataDenFiltersOptions, + DataDenGroupedOptions, DataDenPaginationOptions, DataDenQuickFilterOptions, DataDenSortOptions, } from '../data-den-fetch-options.interface'; -import { deepCopy } from '../../../utils'; +import { deepCopy, groupRows } from '../../../utils'; export class DataDenClientDataLoaderStrategy extends DataDenDataLoaderStrategy { #data: any[]; @@ -18,10 +19,24 @@ export class DataDenClientDataLoaderStrategy extends DataDenDataLoaderStrategy { } getData(options: DataDenFetchOptions): Promise { + return this.filterData(deepCopy(this.#data), options.filtersOptions) .then((filtered) => this.quickFilterData(filtered, options.quickFilterOptions)) .then((quickFiltered) => this.sortData(quickFiltered, options.sortingOptions)) - .then((sorted) => this.paginateData(sorted, options.paginationOptions)); + .then((sorted) => this.groupData(sorted, options.groupedOptions)) + .then((grouped) => this.paginateData(grouped, options.paginationOptions, options.groupedOptions)) + } + + groupData(rows: any[], groupedOptions: DataDenGroupedOptions | undefined): Promise { + if (!groupedOptions) { + return Promise.resolve(rows); + } + + const groups = groupedOptions.groupedColumns.map((column) => column.group); + + const grouped = groupRows(rows, groups); + + return Promise.resolve(grouped); } filterData(rows: any[], filtersOptions: DataDenFiltersOptions | undefined): Promise { @@ -43,14 +58,16 @@ export class DataDenClientDataLoaderStrategy extends DataDenDataLoaderStrategy { return Promise.resolve(filtered); } - paginateData(rows: any[], paginationOptions: DataDenPaginationOptions | undefined): Promise { + paginateData(rows: any[], paginationOptions: DataDenPaginationOptions | undefined, groupedOptions: DataDenGroupedOptions | undefined): Promise { if (!paginationOptions) { return Promise.resolve(rows); } const { firstRowIndex, lastRowIndex } = paginationOptions; - const paginated = rows.slice(firstRowIndex, lastRowIndex); + const paginated = (groupedOptions ? Object.fromEntries( + Object.entries(rows).slice(firstRowIndex, lastRowIndex) + ) : rows.slice(firstRowIndex, lastRowIndex)); return Promise.resolve(paginated); } diff --git a/packages/core/src/modules/fetch/strategy/data-den-data-loader-strategy.ts b/packages/core/src/modules/fetch/strategy/data-den-data-loader-strategy.ts index 13d93a3..fda53a7 100644 --- a/packages/core/src/modules/fetch/strategy/data-den-data-loader-strategy.ts +++ b/packages/core/src/modules/fetch/strategy/data-den-data-loader-strategy.ts @@ -1,6 +1,7 @@ import { DataDenFetchOptions, DataDenFiltersOptions, + DataDenGroupedOptions, DataDenPaginationOptions, DataDenQuickFilterOptions, DataDenSortOptions, @@ -11,5 +12,5 @@ export abstract class DataDenDataLoaderStrategy { abstract filterData(data: any[], params: DataDenFiltersOptions): Promise; abstract sortData(data: any[], params: DataDenSortOptions): Promise; abstract quickFilterData(data: any[], params: DataDenQuickFilterOptions): Promise; - abstract paginateData(data: any[], params: DataDenPaginationOptions): Promise; + abstract paginateData(data: any[], params: DataDenPaginationOptions, groupedOptions: DataDenGroupedOptions): Promise; } diff --git a/packages/core/src/modules/pagination/data-den-pagination-service.ts b/packages/core/src/modules/pagination/data-den-pagination-service.ts index 774a36f..820f8a1 100644 --- a/packages/core/src/modules/pagination/data-den-pagination-service.ts +++ b/packages/core/src/modules/pagination/data-den-pagination-service.ts @@ -32,16 +32,24 @@ export class DataDenPaginationService { this.PubSub.subscribe('command:pagination:load-last-page:start', () => { this.#loadLastPage(); }); + this.PubSub.subscribe('command:group:update', () => { + this.#currentPage = 0; + this.#allTotalRows = 0; + }); this.PubSub.subscribe('info:pagination:page-size-change:done', (event: { data: { pageSize: number } }) => { this.#pageSize = event.data.pageSize; this.#currentPage = 0; this.#updateState(); }); this.PubSub.subscribe('info:fetch:done', (event: DataDenEvent) => { - if (this.#allTotalRows || this.#allTotalRows === event.data.rows.length) { + const rows = event.data.rows; + const length = Object.keys(rows).length; + + if (this.#allTotalRows || this.#allTotalRows === length) { return; } - this.#allTotalRows = event.data.rows.length; + + this.#allTotalRows = length; this.#updateState(); }); } diff --git a/packages/core/src/modules/rendering/cell/data-den-cell-renderer-params.interface.ts b/packages/core/src/modules/rendering/cell/data-den-cell-renderer-params.interface.ts index 78c09cd..d3b2c7b 100644 --- a/packages/core/src/modules/rendering/cell/data-den-cell-renderer-params.interface.ts +++ b/packages/core/src/modules/rendering/cell/data-den-cell-renderer-params.interface.ts @@ -1,4 +1,6 @@ export interface DataDenCellRendererParams { value: any; cssPrefix: string; + isGroupCell?: boolean; + icon?: HTMLElement; } diff --git a/packages/core/src/modules/rendering/cell/data-den-cell.ts b/packages/core/src/modules/rendering/cell/data-den-cell.ts index d85a0ae..94647db 100644 --- a/packages/core/src/modules/rendering/cell/data-den-cell.ts +++ b/packages/core/src/modules/rendering/cell/data-den-cell.ts @@ -18,6 +18,9 @@ export class DataDenCell { cellElements: DataDenCell[] = []; isBlurByKey: boolean = false; prevValue: any; + icon?: HTMLElement; + isGroupCell?: boolean; + constructor( value: any, @@ -26,7 +29,9 @@ export class DataDenCell { left: number, width: number, pinned: string, - options: DataDenInternalOptions + options: DataDenInternalOptions, + icon?: HTMLElement, + isGroupCell?: boolean ) { this.colIndex = colIndex; this.rowIndex = rowIndex; @@ -35,6 +40,8 @@ export class DataDenCell { this.#options = options; this.#left = pinned ? 'auto' : `${left}px`; this.pinned = pinned; + this.icon = icon; + this.isGroupCell = isGroupCell; this.#initRenderers(); } @@ -52,6 +59,8 @@ export class DataDenCell { #getCellRendererParams(): DataDenCellRendererParams { return { + isGroupCell: this.isGroupCell, + icon: this.icon, value: this.#value, cssPrefix: this.#options.cssPrefix, }; @@ -145,11 +154,11 @@ export class DataDenCell { `
${escapeHtml(this.value)}`); + this.icon = params.icon; + this.isGroupCell = params.isGroupCell + + this.element = createHtmlElement(` + ${this.icon ? `
+ ${this.icon.outerHTML} +
` : ''} + ${escapeHtml(this.value)} +
`); } getGui(): HTMLElement { diff --git a/packages/core/src/modules/rendering/data-den-rendering-service.ts b/packages/core/src/modules/rendering/data-den-rendering-service.ts index b81ae59..f4cbb81 100644 --- a/packages/core/src/modules/rendering/data-den-rendering-service.ts +++ b/packages/core/src/modules/rendering/data-den-rendering-service.ts @@ -1,7 +1,7 @@ import { DataDenColDef, DataDenInternalOptions } from '../../data-den-options.interface'; import { DataDenCell, DataDenHeaderCell } from './cell'; import { DataDenPaginationRenderer } from './pagination'; -import { DataDenHeaderRow, DataDenRow } from './row'; +import { DataDenGroupRow, DataDenHeaderRow, DataDenRow } from './row'; import { DataDenPubSub } from '../../data-den-pub-sub'; import { DataDenEvent } from '../../data-den-event'; import { DataDenSortOrder } from '../sorting/data-den-sorting.interface'; @@ -16,20 +16,26 @@ import { } from '../../utils/columns-order'; import { DataDenPinningPreviousState } from '../pinning/data-den-pinning-previous-state'; import { DataDenEventEmitter } from '../../data-den-event-emitter'; +import { countNumOfLevels, createHtmlElement, transformGroupedData } from '../../utils'; +import { DataDenPinnedCellParams } from './row/data-den-pinned.interface'; export class DataDenRenderingService { #container: HTMLElement; #options: DataDenInternalOptions; #orderedColumns: DataDenColDef[]; #defaultOrderedColumns: DataDenColDef[]; + #mainOrderedColumns: DataDenColDef[]; #columnsOrder: number[]; #headerRow: DataDenHeaderRow; #rows: DataDenRow[] = []; #paginationRenderer: DataDenPaginationRenderer | null = null; + #groupedColumns: any = []; constructor(container: HTMLElement, options: DataDenInternalOptions, private PubSub: DataDenPubSub) { this.#container = container; this.#options = options; + this.#mainOrderedColumns = options.columns; + this.#columnsOrder = getMainColumnIndexes(this.#mainOrderedColumns); if (options.pagination) { this.#paginationRenderer = new DataDenPaginationRenderer( @@ -46,10 +52,9 @@ export class DataDenRenderingService { } #init() { - this.#orderedColumns = getMainOrderedColumns(this.#options.columns); - this.#defaultOrderedColumns = getMainOrderedColumns(this.#options.columns); - this.#columnsOrder = getMainColumnIndexes(this.#options.columns); - this.#headerRow = this.#createHeaderRow(this.#options.columns, null); + this.#orderedColumns = getMainOrderedColumns(this.#mainOrderedColumns); + this.#defaultOrderedColumns = getMainOrderedColumns(this.#mainOrderedColumns); + this.#headerRow = this.#createHeaderRow(this.#mainOrderedColumns, null); this.renderTable(); } @@ -120,37 +125,22 @@ export class DataDenRenderingService { return new DataDenHeaderRow(rowIndex, headerCells, this.#options); } - #createPinnedCellsLeft(key: string, value: any, colIndex: number, rowIndex: number): DataDenCell | undefined { - const colDef = this.#options.columns.find((col) => col.field === key)!; - if (colDef.pinned !== 'left') { - return undefined; - } - + #createPinnedCellsLeft({ colIndex, value, rowIndex, colDef, icon, isGroupCell }: DataDenPinnedCellParams): DataDenCell | undefined { const left = 0; const width = this.#options.columns[colIndex].width || 120; - return new DataDenCell(value, colIndex, rowIndex, left, width, colDef.pinned, this.#options); + return new DataDenCell(value, colIndex, rowIndex, left, width, colDef.pinned, this.#options, icon, isGroupCell); } - #createMainCells(key: string, value: any, colIndex: number, rowIndex: number): DataDenCell | undefined { - const colDef = this.#options.columns.find((col) => col.field === key)!; - if (colDef.pinned) { - return undefined; - } - + #createMainCells({ key, colIndex, value, rowIndex, colDef, icon, isGroupCell }: DataDenPinnedCellParams): DataDenCell | undefined { const orderedColIndex = this.#orderedColumns.findIndex((col) => col.field === key); const left = this.#orderedColumns.slice(0, orderedColIndex).reduce((acc, curr) => acc + (curr.width || 120), 0); const width = this.#orderedColumns[orderedColIndex].width || 120; - return new DataDenCell(value, colIndex, rowIndex, left, width, colDef.pinned, this.#options); + return new DataDenCell(value, colIndex, rowIndex, left, width, colDef.pinned, this.#options, icon, isGroupCell); } - #createPinnedCellsRight(key: string, value: any, colIndex: number, rowIndex: number): DataDenCell | undefined { - const colDef = this.#options.columns.find((col) => col.field === key)!; - if (colDef.pinned !== 'right') { - return undefined; - } - + #createPinnedCellsRight({ key, colIndex, value, rowIndex, colDef, icon, isGroupCell }: DataDenPinnedCellParams): DataDenCell | undefined { const pinnedColIndex = this.#options.columns .filter((col) => col.pinned === 'right') .map((defaultColumn) => defaultColumn.field) @@ -159,32 +149,88 @@ export class DataDenRenderingService { const left = 0; const width = getPinnedRightColumns(this.#options.columns)[pinnedColIndex].width || 120; - return new DataDenCell(value, colIndex, rowIndex, left, width, colDef.pinned, this.#options); + return new DataDenCell(value, colIndex, rowIndex, left, width, colDef.pinned, this.#options, icon, isGroupCell); } #createDataRows(rowsData: any): DataDenRow[] { + const numOfCells = (rowsData.length) ? Object.keys(rowsData[rowsData.length - 1]).length : 0; + let group = rowsData.length ? rowsData[0]._group : ''; + const numOfLevels = countNumOfLevels(rowsData); + + let isGroupCell = false, res, activeGroups: string[] = []; + return rowsData.map((rowData: any, rowIndex: number) => { - const pinnedCellsLeft = Object.entries(rowData).map(([key, value], colIndex) => - this.#createPinnedCellsLeft(key, value, colIndex, rowIndex) - ); - const mainCells = Object.entries(rowData); - mainCells.sort(([aField], [bField]) => { - // sort based on this.#orderedColumns order - const aIndex = this.#orderedColumns.findIndex((col) => col.field === aField); - const bIndex = this.#orderedColumns.findIndex((col) => col.field === bField); - return aIndex - bIndex; - }); - const mainCellsSorted = mainCells.map(([key, value], colIndex) => - this.#createMainCells(key, value, colIndex, rowIndex) - ); + const pinnedCellsLeft = [], mainCells = [], pinnedCellsRight = []; + + if (group && rowData._group) { + group = rowData._group; + } + + for (let i = 0; i < numOfCells; i++) { + let key: string; + let value: string; + let icon: HTMLElement | undefined = undefined; + + if (rowData._colIndex === i) { + key = rowData._column; + icon = createHtmlElement(` + + + `); + value = `${rowData._group}`; + isGroupCell = true; + } else if (rowData._group) { + key = this.#options.columns[i].field; + value = ''; + isGroupCell = true; + } else { + key = Object.keys(rowData)[i]; + value = rowData[key]; + isGroupCell = false; + } + + const colDef = this.#options.columns.find((col) => col.field === key)!; + + const params = { + key: key, + value: value, + colIndex: i, + rowIndex, + colDef, + icon, + isGroupCell + }; + + switch (colDef.pinned) { + case 'left': + pinnedCellsLeft.push(this.#createPinnedCellsLeft(params)); + break; + case 'right': + pinnedCellsRight.push(this.#createPinnedCellsRight(params)); + break; + default: + mainCells.push(this.#createMainCells(params)); + } + } + + const cells = [...pinnedCellsLeft, ...mainCells, ...pinnedCellsRight].filter((cell) => cell !== undefined); + + if (rowData._group) { + const level = rowData._level; + + if (level === 0) activeGroups = []; + + activeGroups[level] = group; - const pinnedCellsRight = Object.entries(rowData) - .reverse() - .map(([key, value], colIndex) => this.#createPinnedCellsRight(key, value, colIndex, rowIndex)); + const parents = activeGroups.slice(0, level + 1); - const cells = [...pinnedCellsLeft, ...mainCellsSorted, ...pinnedCellsRight].filter((cell) => cell !== undefined); + res = new DataDenGroupRow(rowIndex, cells, this.#options, group, parents, numOfLevels); - return new DataDenRow(rowIndex, cells, this.#options); + } else { + res = new DataDenRow(rowIndex, cells, this.#options, activeGroups); + } + + return res; }); } @@ -279,7 +325,8 @@ export class DataDenRenderingService { #renderEditor(e: MouseEvent): HTMLElement { const target = e.target as HTMLElement; - if (target.tagName !== 'SPAN') return; + + if (target.tagName !== 'SPAN' || target.classList.contains(`${this.#options.cssPrefix}group`)) return; const cellElement = target.parentElement; const rowIndex = Number(cellElement.getAttribute('rowIndex')); @@ -327,6 +374,16 @@ export class DataDenRenderingService { return rowContainer; } + #updateGroups(pageX: number) { + this.PubSub.publish('command:group:update', { + pageX: pageX, + columnsOrder: this.#columnsOrder, + mainOrderedColumns: this.#mainOrderedColumns, + groupedColumns: this.#groupedColumns, + context: new Context('command:group:update'), + }); + } + #subscribeToEvents(): void { this.PubSub.subscribe('info:dragging:columns-reorder:done', (event: DataDenEvent) => { this.#columnsOrder = event.data.columnsOrder; @@ -362,6 +419,34 @@ export class DataDenRenderingService { columns: this.#options.columns, }); }); + this.PubSub.subscribe('command:group-column:start', (event: DataDenEvent) => { + const data = event.data; + const group = data.group; + const colIndex = data.colIndex; + const level = this.#groupedColumns.length; + + this.#groupedColumns.push({ colIndex: colIndex, group: group, level: level }); + + const groupedColumns = this.#groupedColumns.map((column: any) => { + return this.#options.columns[column.colIndex]; + }); + + this.#mainOrderedColumns = [...new Set(groupedColumns.concat(this.#mainOrderedColumns))]; + this.#columnsOrder.unshift(colIndex); + this.#columnsOrder = [...new Set(this.#columnsOrder)]; + + this.#options.columns[colIndex].grouped = true; + this.#updateGroups(event.data.pageX); + this.rerenderTable(); + + }); + this.PubSub.subscribe('command:ungroup-column:start', (event: DataDenEvent) => { + this.#groupedColumns = this.#groupedColumns.filter((column: any) => column.group !== event.data.group); + this.#options.columns[event.data.colIndex].grouped = false; + + this.#updateGroups(event.data.pageX); + this.rerenderTable(); + }); } #subscribeFetchDone(): void { @@ -371,7 +456,7 @@ export class DataDenRenderingService { } #updateRows(event: DataDenEvent): void { - const { rows } = event.data; + const rows = transformGroupedData(event.data.rows, this.#groupedColumns); const rowsEl = document.createDocumentFragment(); this.#rows = this.#createDataRows(rows); @@ -383,3 +468,4 @@ export class DataDenRenderingService { this.#calculateGridSize(); } } + diff --git a/packages/core/src/modules/rendering/menu/data-den-header-menu-renderer.ts b/packages/core/src/modules/rendering/menu/data-den-header-menu-renderer.ts index 6b09d4e..cdb6090 100644 --- a/packages/core/src/modules/rendering/menu/data-den-header-menu-renderer.ts +++ b/packages/core/src/modules/rendering/menu/data-den-header-menu-renderer.ts @@ -19,8 +19,7 @@ export class DataDenHeaderMenuRenderer { this.colIndex = colIndex; const template = `